summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2013-11-22 10:35:20 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2013-11-22 10:35:20 -0800
commit5bf2560ce9b70bee077e0c264ac06648f0f63acc (patch)
treef17ecec5321e8d583d045135f29f14f3c7418e71 /media
parenteb76f318e9daf91dbf195bcb74852b3bd736a32a (diff)
parentb2059ff384eee8ffb70a7ec8fc5570405201c734 (diff)
downloadframeworks_av-5bf2560ce9b70bee077e0c264ac06648f0f63acc.zip
frameworks_av-5bf2560ce9b70bee077e0c264ac06648f0f63acc.tar.gz
frameworks_av-5bf2560ce9b70bee077e0c264ac06648f0f63acc.tar.bz2
Merge commit 'b2059ff384eee8ffb70a7ec8fc5570405201c734' into HEAD
Diffstat (limited to 'media')
-rw-r--r--media/libcpustats/Android.mk11
-rw-r--r--media/libcpustats/CentralTendencyStatistics.cpp81
-rw-r--r--media/libcpustats/ThreadCpuUsage.cpp255
-rw-r--r--media/libeffects/data/audio_effects.conf46
-rw-r--r--media/libeffects/factory/EffectsFactory.c218
-rw-r--r--media/libeffects/factory/EffectsFactory.h19
-rw-r--r--media/libeffects/loudness/Android.mk27
-rw-r--r--media/libeffects/loudness/EffectLoudnessEnhancer.cpp466
-rw-r--r--media/libeffects/loudness/MODULE_LICENSE_APACHE20
-rw-r--r--media/libeffects/loudness/NOTICE190
-rw-r--r--media/libeffects/loudness/common/core/basic_types.h114
-rw-r--r--media/libeffects/loudness/common/core/byte_swapper.h151
-rw-r--r--media/libeffects/loudness/common/core/math.h89
-rw-r--r--media/libeffects/loudness/common/core/os.h29
-rw-r--r--media/libeffects/loudness/common/core/types.h31
-rw-r--r--media/libeffects/loudness/dsp/core/basic-inl.h48
-rw-r--r--media/libeffects/loudness/dsp/core/basic.h48
-rw-r--r--media/libeffects/loudness/dsp/core/dynamic_range_compression-inl.h45
-rw-r--r--media/libeffects/loudness/dsp/core/dynamic_range_compression.cpp141
-rw-r--r--media/libeffects/loudness/dsp/core/dynamic_range_compression.h119
-rw-r--r--media/libeffects/loudness/dsp/core/interpolation.h24
-rw-r--r--media/libeffects/loudness/dsp/core/interpolator_base-inl.h180
-rw-r--r--media/libeffects/loudness/dsp/core/interpolator_base.h112
-rw-r--r--media/libeffects/loudness/dsp/core/interpolator_linear.h81
-rw-r--r--media/libeffects/proxy/Android.mk34
-rw-r--r--media/libeffects/proxy/EffectProxy.cpp339
-rw-r--r--media/libeffects/proxy/EffectProxy.h80
-rw-r--r--media/libeffects/testlibs/AudioFormatAdapter.h1
-rw-r--r--media/libeffects/testlibs/EffectEqualizer.cpp3
-rw-r--r--media/libeffects/visualizer/EffectVisualizer.cpp156
-rw-r--r--media/libmedia/Android.mk4
-rw-r--r--media/libmedia/AudioRecord.cpp1006
-rw-r--r--media/libmedia/AudioSystem.cpp53
-rw-r--r--media/libmedia/AudioTrack.cpp1451
-rw-r--r--media/libmedia/AudioTrackShared.cpp887
-rw-r--r--media/libmedia/IAudioFlinger.cpp127
-rw-r--r--media/libmedia/IAudioFlingerClient.cpp6
-rw-r--r--media/libmedia/IAudioPolicyService.cpp38
-rw-r--r--media/libmedia/IAudioRecord.cpp23
-rw-r--r--media/libmedia/IAudioTrack.cpp58
-rw-r--r--media/libmedia/IDrm.cpp7
-rw-r--r--media/libmedia/IHDCP.cpp70
-rw-r--r--media/libmedia/IMediaDeathNotifier.cpp8
-rw-r--r--media/libmedia/IMediaPlayerService.cpp73
-rw-r--r--media/libmedia/IOMX.cpp96
-rw-r--r--media/libmedia/IRemoteDisplayClient.cpp6
-rw-r--r--media/libmedia/JetPlayer.cpp8
-rw-r--r--media/libmedia/MediaScannerClient.cpp2
-rw-r--r--media/libmedia/SingleStateQueueInstantiations.cpp2
-rw-r--r--media/libmedia/SoundPool.cpp73
-rw-r--r--media/libmedia/StringArray.cpp113
-rw-r--r--media/libmedia/StringArray.h83
-rw-r--r--media/libmedia/ToneGenerator.cpp30
-rw-r--r--media/libmedia/Visualizer.cpp69
-rw-r--r--media/libmedia/mediaplayer.cpp34
-rw-r--r--media/libmediaplayerservice/Android.mk1
-rw-r--r--media/libmediaplayerservice/Crypto.cpp3
-rw-r--r--media/libmediaplayerservice/Drm.cpp17
-rw-r--r--media/libmediaplayerservice/Drm.h2
-rw-r--r--media/libmediaplayerservice/HDCP.cpp32
-rw-r--r--media/libmediaplayerservice/HDCP.h6
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.cpp374
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.h35
-rw-r--r--media/libmediaplayerservice/MidiFile.cpp8
-rw-r--r--media/libmediaplayerservice/RemoteDisplay.cpp13
-rw-r--r--media/libmediaplayerservice/RemoteDisplay.h5
-rw-r--r--media/libmediaplayerservice/SharedLibrary.cpp6
-rw-r--r--media/libmediaplayerservice/SharedLibrary.h1
-rw-r--r--media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp194
-rw-r--r--media/libmediaplayerservice/nuplayer/HTTPLiveSource.h12
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayer.cpp183
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayer.h13
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp27
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDriver.h2
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp36
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h6
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerSource.h10
-rw-r--r--media/libmediaplayerservice/nuplayer/RTSPSource.cpp9
-rw-r--r--media/libnbaio/Android.mk5
-rw-r--r--media/libnbaio/AudioStreamOutSink.cpp15
-rw-r--r--media/libnbaio/MonoPipe.cpp13
-rw-r--r--media/libnbaio/MonoPipeReader.cpp5
-rw-r--r--media/libnbaio/SourceAudioBufferProvider.cpp13
-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
178 files changed, 13126 insertions, 4065 deletions
diff --git a/media/libcpustats/Android.mk b/media/libcpustats/Android.mk
new file mode 100644
index 0000000..b506353
--- /dev/null
+++ b/media/libcpustats/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ CentralTendencyStatistics.cpp \
+ ThreadCpuUsage.cpp
+
+LOCAL_MODULE := libcpustats
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/media/libcpustats/CentralTendencyStatistics.cpp b/media/libcpustats/CentralTendencyStatistics.cpp
new file mode 100644
index 0000000..42ab62b
--- /dev/null
+++ b/media/libcpustats/CentralTendencyStatistics.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <cpustats/CentralTendencyStatistics.h>
+
+void CentralTendencyStatistics::sample(double x)
+{
+ // update min and max
+ if (x < mMinimum)
+ mMinimum = x;
+ if (x > mMaximum)
+ mMaximum = x;
+ // Knuth
+ if (mN == 0) {
+ mMean = 0;
+ }
+ ++mN;
+ double delta = x - mMean;
+ mMean += delta / mN;
+ mM2 += delta * (x - mMean);
+}
+
+void CentralTendencyStatistics::reset()
+{
+ mMean = NAN;
+ mMedian = NAN;
+ mMinimum = INFINITY;
+ mMaximum = -INFINITY;
+ mN = 0;
+ mM2 = 0;
+ mVariance = NAN;
+ mVarianceKnownForN = 0;
+ mStddev = NAN;
+ mStddevKnownForN = 0;
+}
+
+double CentralTendencyStatistics::variance() const
+{
+ double variance;
+ if (mVarianceKnownForN != mN) {
+ if (mN > 1) {
+ // double variance_n = M2/n;
+ variance = mM2 / (mN - 1);
+ } else {
+ variance = NAN;
+ }
+ mVariance = variance;
+ mVarianceKnownForN = mN;
+ } else {
+ variance = mVariance;
+ }
+ return variance;
+}
+
+double CentralTendencyStatistics::stddev() const
+{
+ double stddev;
+ if (mStddevKnownForN != mN) {
+ stddev = sqrt(variance());
+ mStddev = stddev;
+ mStddevKnownForN = mN;
+ } else {
+ stddev = mStddev;
+ }
+ return stddev;
+}
diff --git a/media/libcpustats/ThreadCpuUsage.cpp b/media/libcpustats/ThreadCpuUsage.cpp
new file mode 100644
index 0000000..637402a
--- /dev/null
+++ b/media/libcpustats/ThreadCpuUsage.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ThreadCpuUsage"
+//#define LOG_NDEBUG 0
+
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <utils/Debug.h>
+#include <utils/Log.h>
+
+#include <cpustats/ThreadCpuUsage.h>
+
+namespace android {
+
+bool ThreadCpuUsage::setEnabled(bool isEnabled)
+{
+ bool wasEnabled = mIsEnabled;
+ // only do something if there is a change
+ if (isEnabled != wasEnabled) {
+ ALOGV("setEnabled(%d)", isEnabled);
+ int rc;
+ // enabling
+ if (isEnabled) {
+ rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &mPreviousTs);
+ if (rc) {
+ ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+ isEnabled = false;
+ } else {
+ mWasEverEnabled = true;
+ // record wall clock time at first enable
+ if (!mMonotonicKnown) {
+ rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
+ if (rc) {
+ ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+ } else {
+ mMonotonicKnown = true;
+ }
+ }
+ }
+ // disabling
+ } else {
+ struct timespec ts;
+ rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
+ if (rc) {
+ ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+ } else {
+ long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
+ (ts.tv_nsec - mPreviousTs.tv_nsec);
+ mAccumulator += delta;
+#if 0
+ mPreviousTs = ts;
+#endif
+ }
+ }
+ mIsEnabled = isEnabled;
+ }
+ return wasEnabled;
+}
+
+bool ThreadCpuUsage::sampleAndEnable(double& ns)
+{
+ bool ret;
+ bool wasEverEnabled = mWasEverEnabled;
+ if (enable()) {
+ // already enabled, so add a new sample relative to previous
+ return sample(ns);
+ } else if (wasEverEnabled) {
+ // was disabled, but add sample for accumulated time while enabled
+ ns = (double) mAccumulator;
+ mAccumulator = 0;
+ ALOGV("sampleAndEnable %.0f", ns);
+ return true;
+ } else {
+ // first time called
+ ns = 0.0;
+ ALOGV("sampleAndEnable false");
+ return false;
+ }
+}
+
+bool ThreadCpuUsage::sample(double &ns)
+{
+ if (mWasEverEnabled) {
+ if (mIsEnabled) {
+ struct timespec ts;
+ int rc;
+ rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
+ if (rc) {
+ ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+ ns = 0.0;
+ return false;
+ } else {
+ long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
+ (ts.tv_nsec - mPreviousTs.tv_nsec);
+ mAccumulator += delta;
+ mPreviousTs = ts;
+ }
+ } else {
+ mWasEverEnabled = false;
+ }
+ ns = (double) mAccumulator;
+ ALOGV("sample %.0f", ns);
+ mAccumulator = 0;
+ return true;
+ } else {
+ ALOGW("Can't add sample because measurements have never been enabled");
+ ns = 0.0;
+ return false;
+ }
+}
+
+long long ThreadCpuUsage::elapsed() const
+{
+ long long elapsed;
+ if (mMonotonicKnown) {
+ struct timespec ts;
+ int rc;
+ rc = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (rc) {
+ ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+ elapsed = 0;
+ } else {
+ // mMonotonicTs is updated only at first enable and resetStatistics
+ elapsed = (ts.tv_sec - mMonotonicTs.tv_sec) * 1000000000LL +
+ (ts.tv_nsec - mMonotonicTs.tv_nsec);
+ }
+ } else {
+ ALOGW("Can't compute elapsed time because measurements have never been enabled");
+ elapsed = 0;
+ }
+ ALOGV("elapsed %lld", elapsed);
+ return elapsed;
+}
+
+void ThreadCpuUsage::resetElapsed()
+{
+ ALOGV("resetElapsed");
+ if (mMonotonicKnown) {
+ int rc;
+ rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
+ if (rc) {
+ ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+ mMonotonicKnown = false;
+ }
+ }
+}
+
+/*static*/
+int ThreadCpuUsage::sScalingFds[ThreadCpuUsage::MAX_CPU];
+pthread_once_t ThreadCpuUsage::sOnceControl = PTHREAD_ONCE_INIT;
+int ThreadCpuUsage::sKernelMax;
+pthread_mutex_t ThreadCpuUsage::sMutex = PTHREAD_MUTEX_INITIALIZER;
+
+/*static*/
+void ThreadCpuUsage::init()
+{
+ // read the number of CPUs
+ sKernelMax = 1;
+ int fd = open("/sys/devices/system/cpu/kernel_max", O_RDONLY);
+ if (fd >= 0) {
+#define KERNEL_MAX_SIZE 12
+ char kernelMax[KERNEL_MAX_SIZE];
+ ssize_t actual = read(fd, kernelMax, sizeof(kernelMax));
+ if (actual >= 2 && kernelMax[actual-1] == '\n') {
+ sKernelMax = atoi(kernelMax);
+ if (sKernelMax >= MAX_CPU - 1) {
+ ALOGW("kernel_max %d but MAX_CPU %d", sKernelMax, MAX_CPU);
+ sKernelMax = MAX_CPU;
+ } else if (sKernelMax < 0) {
+ ALOGW("kernel_max invalid %d", sKernelMax);
+ sKernelMax = 1;
+ } else {
+ ++sKernelMax;
+ ALOGV("number of CPUs %d", sKernelMax);
+ }
+ } else {
+ ALOGW("Can't read number of CPUs");
+ }
+ (void) close(fd);
+ } else {
+ ALOGW("Can't open number of CPUs");
+ }
+ int i;
+ for (i = 0; i < MAX_CPU; ++i) {
+ sScalingFds[i] = -1;
+ }
+}
+
+uint32_t ThreadCpuUsage::getCpukHz(int cpuNum)
+{
+ if (cpuNum < 0 || cpuNum >= MAX_CPU) {
+ ALOGW("getCpukHz called with invalid CPU %d", cpuNum);
+ return 0;
+ }
+ // double-checked locking idiom is not broken for atomic values such as fd
+ int fd = sScalingFds[cpuNum];
+ if (fd < 0) {
+ // some kernels can't open a scaling file until hot plug complete
+ pthread_mutex_lock(&sMutex);
+ fd = sScalingFds[cpuNum];
+ if (fd < 0) {
+#define FREQ_SIZE 64
+ char freq_path[FREQ_SIZE];
+#define FREQ_DIGIT 27
+ COMPILE_TIME_ASSERT_FUNCTION_SCOPE(MAX_CPU <= 10);
+#define FREQ_PATH "/sys/devices/system/cpu/cpu?/cpufreq/scaling_cur_freq"
+ strlcpy(freq_path, FREQ_PATH, sizeof(freq_path));
+ freq_path[FREQ_DIGIT] = cpuNum + '0';
+ fd = open(freq_path, O_RDONLY | O_CLOEXEC);
+ // keep this fd until process exit or exec
+ sScalingFds[cpuNum] = fd;
+ }
+ pthread_mutex_unlock(&sMutex);
+ if (fd < 0) {
+ ALOGW("getCpukHz can't open CPU %d", cpuNum);
+ return 0;
+ }
+ }
+#define KHZ_SIZE 12
+ char kHz[KHZ_SIZE]; // kHz base 10
+ ssize_t actual = pread(fd, kHz, sizeof(kHz), (off_t) 0);
+ uint32_t ret;
+ if (actual >= 2 && kHz[actual-1] == '\n') {
+ ret = atoi(kHz);
+ } else {
+ ret = 0;
+ }
+ if (ret != mCurrentkHz[cpuNum]) {
+ if (ret > 0) {
+ ALOGV("CPU %d frequency %u kHz", cpuNum, ret);
+ } else {
+ ALOGW("Can't read CPU %d frequency", cpuNum);
+ }
+ mCurrentkHz[cpuNum] = ret;
+ }
+ return ret;
+}
+
+} // namespace android
diff --git a/media/libeffects/data/audio_effects.conf b/media/libeffects/data/audio_effects.conf
index 93f27cb..c3c4b67 100644
--- a/media/libeffects/data/audio_effects.conf
+++ b/media/libeffects/data/audio_effects.conf
@@ -6,6 +6,23 @@
# }
# }
libraries {
+# This is a proxy library that will be an abstraction for
+# the HW and SW effects
+
+ #proxy {
+ #path /system/lib/soundfx/libeffectproxy.so
+ #}
+
+# This is the SW implementation library of the effect
+ #libSW {
+ #path /system/lib/soundfx/libswwrapper.so
+ #}
+
+# This is the HW implementation library for the effect
+ #libHW {
+ #path /system/lib/soundfx/libhwwrapper.so
+ #}
+
bundle {
path /system/lib/soundfx/libbundlewrapper.so
}
@@ -18,6 +35,9 @@ libraries {
downmix {
path /system/lib/soundfx/libdownmix.so
}
+ loudness_enhancer {
+ path /system/lib/soundfx/libldnhncr.so
+ }
}
# Default pre-processing library. Add to audio_effect.conf "libraries" section if
@@ -43,6 +63,28 @@ libraries {
# }
effects {
+
+# additions for the proxy implementation
+# Proxy implementation
+ #effectname {
+ #library proxy
+ #uuid xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+
+ # SW implemetation of the effect. Added as a node under the proxy to
+ # indicate this as a sub effect.
+ #libsw {
+ #library libSW
+ #uuid yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
+ #} End of SW effect
+
+ # HW implementation of the effect. Added as a node under the proxy to
+ # indicate this as a sub effect.
+ #libhw {
+ #library libHW
+ #uuid zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
+ #}End of HW effect
+ #} End of effect proxy
+
bassboost {
library bundle
uuid 8631f300-72e2-11df-b57e-0002a5d5c51b
@@ -83,6 +125,10 @@ effects {
library downmix
uuid 93f04452-e4fe-41cc-91f9-e475b6d1d69f
}
+ loudness_enhancer {
+ library loudness_enhancer
+ uuid fa415329-2034-4bea-b5dc-5b381c8d1e2c
+ }
}
# Default pre-processing effects. Add to audio_effect.conf "effects" section if
diff --git a/media/libeffects/factory/EffectsFactory.c b/media/libeffects/factory/EffectsFactory.c
index f158929..f8d6041 100644
--- a/media/libeffects/factory/EffectsFactory.c
+++ b/media/libeffects/factory/EffectsFactory.c
@@ -28,6 +28,9 @@
static list_elem_t *gEffectList; // list of effect_entry_t: all currently created effects
static list_elem_t *gLibraryList; // list of lib_entry_t: all currently loaded libraries
+// list of effect_descriptor and list of sub effects : all currently loaded
+// It does not contain effects without sub effects.
+static list_sub_elem_t *gSubEffectList;
static pthread_mutex_t gLibLock = PTHREAD_MUTEX_INITIALIZER; // controls access to gLibraryList
static uint32_t gNumEffects; // total number number of effects
static list_elem_t *gCurLib; // current library in enumeration process
@@ -50,6 +53,8 @@ static int loadLibraries(cnode *root);
static int loadLibrary(cnode *root, const char *name);
static int loadEffects(cnode *root);
static int loadEffect(cnode *node);
+// To get and add the effect pointed by the passed node to the gSubEffectList
+static int addSubEffect(cnode *root);
static lib_entry_t *getLibrary(const char *path);
static void resetEffectEnumeration();
static uint32_t updateNumEffects();
@@ -57,6 +62,10 @@ static int findEffect(const effect_uuid_t *type,
const effect_uuid_t *uuid,
lib_entry_t **lib,
effect_descriptor_t **desc);
+// To search a subeffect in the gSubEffectList
+int findSubEffect(const effect_uuid_t *uuid,
+ lib_entry_t **lib,
+ effect_descriptor_t **desc);
static void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len);
static int stringToUuid(const char *str, effect_uuid_t *uuid);
static int uuidToString(const effect_uuid_t *uuid, char *str, size_t maxLen);
@@ -287,7 +296,12 @@ int EffectCreate(const effect_uuid_t *uuid, int32_t sessionId, int32_t ioId, eff
ret = findEffect(NULL, uuid, &l, &d);
if (ret < 0){
- goto exit;
+ // Sub effects are not associated with the library->effects,
+ // so, findEffect will fail. Search for the effect in gSubEffectList.
+ ret = findSubEffect(uuid, &l, &d);
+ if (ret < 0 ) {
+ goto exit;
+ }
}
// create effect in library
@@ -354,21 +368,27 @@ int EffectRelease(effect_handle_t handle)
}
if (e1 == NULL) {
ret = -ENOENT;
+ pthread_mutex_unlock(&gLibLock);
goto exit;
}
// release effect in library
if (fx->lib == NULL) {
ALOGW("EffectRelease() fx %p library already unloaded", handle);
+ pthread_mutex_unlock(&gLibLock);
} else {
pthread_mutex_lock(&fx->lib->lock);
+ // Releasing the gLibLock here as the list access is over as the
+ // effect is removed from the list.
+ // If the gLibLock is not released, we will have a deadlock situation
+ // since we call the sub effect release inside the EffectRelease of Proxy
+ pthread_mutex_unlock(&gLibLock);
fx->lib->desc->release_effect(fx->subItfe);
pthread_mutex_unlock(&fx->lib->lock);
}
free(fx);
exit:
- pthread_mutex_unlock(&gLibLock);
return ret;
}
@@ -380,6 +400,49 @@ int EffectIsNullUuid(const effect_uuid_t *uuid)
return 1;
}
+// Function to get the sub effect descriptors of the effect whose uuid
+// is pointed by the first argument. It searches the gSubEffectList for the
+// matching uuid and then copies the corresponding sub effect descriptors
+// to the inout param
+int EffectGetSubEffects(const effect_uuid_t *uuid,
+ effect_descriptor_t *pDescriptors, size_t size)
+{
+ ALOGV("EffectGetSubEffects() UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X"
+ "%02X\n",uuid->timeLow, uuid->timeMid, uuid->timeHiAndVersion,
+ uuid->clockSeq, uuid->node[0], uuid->node[1],uuid->node[2],
+ uuid->node[3],uuid->node[4],uuid->node[5]);
+
+ // Check if the size of the desc buffer is large enough for 2 subeffects
+ if ((uuid == NULL) || (pDescriptors == NULL) ||
+ (size < 2*sizeof(effect_descriptor_t))) {
+ ALOGW("NULL pointer or insufficient memory. Cannot query subeffects");
+ return -EINVAL;
+ }
+ int ret = init();
+ if (ret < 0)
+ return ret;
+ list_sub_elem_t *e = gSubEffectList;
+ sub_effect_entry_t *subeffect;
+ effect_descriptor_t *d;
+ int count = 0;
+ while (e != NULL) {
+ d = (effect_descriptor_t*)e->object;
+ if (memcmp(uuid, &d->uuid, sizeof(effect_uuid_t)) == 0) {
+ ALOGV("EffectGetSubEffects: effect found in the list");
+ list_elem_t *subefx = e->sub_elem;
+ while (subefx != NULL) {
+ subeffect = (sub_effect_entry_t*)subefx->object;
+ d = (effect_descriptor_t*)(subeffect->object);
+ pDescriptors[count++] = *d;
+ subefx = subefx->next;
+ }
+ ALOGV("EffectGetSubEffects end - copied the sub effect descriptors");
+ return count;
+ }
+ e = e->next;
+ }
+ return -ENOENT;
+}
/////////////////////////////////////////////////
// Local functions
/////////////////////////////////////////////////
@@ -503,6 +566,65 @@ error:
return -EINVAL;
}
+// This will find the library and UUID tags of the sub effect pointed by the
+// node, gets the effect descriptor and lib_entry_t and adds the subeffect -
+// sub_entry_t to the gSubEffectList
+int addSubEffect(cnode *root)
+{
+ ALOGV("addSubEffect");
+ cnode *node;
+ effect_uuid_t uuid;
+ effect_descriptor_t *d;
+ lib_entry_t *l;
+ list_elem_t *e;
+ node = config_find(root, LIBRARY_TAG);
+ if (node == NULL) {
+ return -EINVAL;
+ }
+ l = getLibrary(node->value);
+ if (l == NULL) {
+ ALOGW("addSubEffect() could not get library %s", node->value);
+ return -EINVAL;
+ }
+ node = config_find(root, UUID_TAG);
+ if (node == NULL) {
+ return -EINVAL;
+ }
+ if (stringToUuid(node->value, &uuid) != 0) {
+ ALOGW("addSubEffect() invalid uuid %s", node->value);
+ return -EINVAL;
+ }
+ d = malloc(sizeof(effect_descriptor_t));
+ if (l->desc->get_descriptor(&uuid, d) != 0) {
+ char s[40];
+ uuidToString(&uuid, s, 40);
+ ALOGW("Error querying effect %s on lib %s", s, l->name);
+ free(d);
+ return -EINVAL;
+ }
+#if (LOG_NDEBUG==0)
+ char s[256];
+ dumpEffectDescriptor(d, s, 256);
+ ALOGV("addSubEffect() read descriptor %p:%s",d, s);
+#endif
+ if (EFFECT_API_VERSION_MAJOR(d->apiVersion) !=
+ EFFECT_API_VERSION_MAJOR(EFFECT_CONTROL_API_VERSION)) {
+ ALOGW("Bad API version %08x on lib %s", d->apiVersion, l->name);
+ free(d);
+ return -EINVAL;
+ }
+ sub_effect_entry_t *sub_effect = malloc(sizeof(sub_effect_entry_t));
+ sub_effect->object = d;
+ // lib_entry_t is stored since the sub effects are not linked to the library
+ sub_effect->lib = l;
+ e = malloc(sizeof(list_elem_t));
+ e->object = sub_effect;
+ e->next = gSubEffectList->sub_elem;
+ gSubEffectList->sub_elem = e;
+ ALOGV("addSubEffect end");
+ return 0;
+}
+
int loadEffects(cnode *root)
{
cnode *node;
@@ -571,9 +693,101 @@ int loadEffect(cnode *root)
e->next = l->effects;
l->effects = e;
+ // After the UUID node in the config_tree, if node->next is valid,
+ // that would be sub effect node.
+ // Find the sub effects and add them to the gSubEffectList
+ node = node->next;
+ int count = 2;
+ bool hwSubefx = false, swSubefx = false;
+ list_sub_elem_t *sube = NULL;
+ if (node != NULL) {
+ ALOGV("Adding the effect to gEffectSubList as there are sub effects");
+ sube = malloc(sizeof(list_sub_elem_t));
+ sube->object = d;
+ sube->sub_elem = NULL;
+ sube->next = gSubEffectList;
+ gSubEffectList = sube;
+ }
+ while (node != NULL && count) {
+ if (addSubEffect(node)) {
+ ALOGW("loadEffect() could not add subEffect %s", node->value);
+ // Change the gSubEffectList to point to older list;
+ gSubEffectList = sube->next;
+ free(sube->sub_elem);// Free an already added sub effect
+ sube->sub_elem = NULL;
+ free(sube);
+ return -ENOENT;
+ }
+ sub_effect_entry_t *subEntry = (sub_effect_entry_t*)gSubEffectList->sub_elem->object;
+ effect_descriptor_t *subEffectDesc = (effect_descriptor_t*)(subEntry->object);
+ // Since we return a dummy descriptor for the proxy during
+ // get_descriptor call,we replace it with the correspoding
+ // sw effect descriptor, but with Proxy UUID
+ // check for Sw desc
+ if (!((subEffectDesc->flags & EFFECT_FLAG_HW_ACC_MASK) ==
+ EFFECT_FLAG_HW_ACC_TUNNEL)) {
+ swSubefx = true;
+ *d = *subEffectDesc;
+ d->uuid = uuid;
+ ALOGV("loadEffect() Changed the Proxy desc");
+ } else
+ hwSubefx = true;
+ count--;
+ node = node->next;
+ }
+ // 1 HW and 1 SW sub effect found. Set the offload flag in the Proxy desc
+ if (hwSubefx && swSubefx) {
+ d->flags |= EFFECT_FLAG_OFFLOAD_SUPPORTED;
+ }
return 0;
}
+// Searches the sub effect matching to the specified uuid
+// in the gSubEffectList. It gets the lib_entry_t for
+// the matched sub_effect . Used in EffectCreate of sub effects
+int findSubEffect(const effect_uuid_t *uuid,
+ lib_entry_t **lib,
+ effect_descriptor_t **desc)
+{
+ list_sub_elem_t *e = gSubEffectList;
+ list_elem_t *subefx;
+ sub_effect_entry_t *effect;
+ lib_entry_t *l = NULL;
+ effect_descriptor_t *d = NULL;
+ int found = 0;
+ int ret = 0;
+
+ if (uuid == NULL)
+ return -EINVAL;
+
+ while (e != NULL && !found) {
+ subefx = (list_elem_t*)(e->sub_elem);
+ while (subefx != NULL) {
+ effect = (sub_effect_entry_t*)subefx->object;
+ l = (lib_entry_t *)effect->lib;
+ d = (effect_descriptor_t *)effect->object;
+ if (memcmp(&d->uuid, uuid, sizeof(effect_uuid_t)) == 0) {
+ ALOGV("uuid matched");
+ found = 1;
+ break;
+ }
+ subefx = subefx->next;
+ }
+ e = e->next;
+ }
+ if (!found) {
+ ALOGV("findSubEffect() effect not found");
+ ret = -ENOENT;
+ } else {
+ ALOGV("findSubEffect() found effect: %s in lib %s", d->name, l->name);
+ *lib = l;
+ if (desc != NULL) {
+ *desc = d;
+ }
+ }
+ return ret;
+}
+
lib_entry_t *getLibrary(const char *name)
{
list_elem_t *e;
diff --git a/media/libeffects/factory/EffectsFactory.h b/media/libeffects/factory/EffectsFactory.h
index c1d4319..147ff18 100644
--- a/media/libeffects/factory/EffectsFactory.h
+++ b/media/libeffects/factory/EffectsFactory.h
@@ -32,6 +32,15 @@ typedef struct list_elem_s {
struct list_elem_s *next;
} list_elem_t;
+// Structure used for storing effects with their sub effects.
+// Used in creating gSubEffectList. Here,
+// object holds the effect desc and the list sub_elem holds the sub effects
+typedef struct list_sub_elem_s {
+ void *object;
+ list_elem_t *sub_elem;
+ struct list_sub_elem_s *next;
+} list_sub_elem_t;
+
typedef struct lib_entry_s {
audio_effect_library_t *desc;
char *name;
@@ -47,6 +56,16 @@ typedef struct effect_entry_s {
lib_entry_t *lib;
} effect_entry_t;
+// Structure used to store the lib entry
+// and the descriptor of the sub effects.
+// The library entry is to be stored in case of
+// sub effects as the sub effects are not linked
+// to the library list - gLibraryList.
+typedef struct sub_effect_entry_s {
+ lib_entry_t *lib;
+ void *object;
+} sub_effect_entry_t;
+
#if __cplusplus
} // extern "C"
#endif
diff --git a/media/libeffects/loudness/Android.mk b/media/libeffects/loudness/Android.mk
new file mode 100644
index 0000000..dcb7b27
--- /dev/null
+++ b/media/libeffects/loudness/Android.mk
@@ -0,0 +1,27 @@
+LOCAL_PATH:= $(call my-dir)
+
+# LoudnessEnhancer library
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ EffectLoudnessEnhancer.cpp \
+ dsp/core/dynamic_range_compression.cpp
+
+LOCAL_CFLAGS+= -O2 -fvisibility=hidden
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ liblog \
+ libstlport
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx
+LOCAL_MODULE:= libldnhncr
+
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, audio-effects) \
+ bionic \
+ bionic/libstdc++/include \
+ external/stlport/stlport
+
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libeffects/loudness/EffectLoudnessEnhancer.cpp b/media/libeffects/loudness/EffectLoudnessEnhancer.cpp
new file mode 100644
index 0000000..91ed677
--- /dev/null
+++ b/media/libeffects/loudness/EffectLoudnessEnhancer.cpp
@@ -0,0 +1,466 @@
+/*
+ * 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_TAG "EffectLE"
+//#define LOG_NDEBUG 0
+#include <cutils/log.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <new>
+#include <time.h>
+#include <math.h>
+#include <audio_effects/effect_loudnessenhancer.h>
+#include "dsp/core/dynamic_range_compression.h"
+
+extern "C" {
+
+// effect_handle_t interface implementation for LE effect
+extern const struct effect_interface_s gLEInterface;
+
+// AOSP Loudness Enhancer UUID: fa415329-2034-4bea-b5dc-5b381c8d1e2c
+const effect_descriptor_t gLEDescriptor = {
+ {0xfe3199be, 0xaed0, 0x413f, 0x87bb, {0x11, 0x26, 0x0e, 0xb6, 0x3c, 0xf1}}, // type
+ {0xfa415329, 0x2034, 0x4bea, 0xb5dc, {0x5b, 0x38, 0x1c, 0x8d, 0x1e, 0x2c}}, // uuid
+ EFFECT_CONTROL_API_VERSION,
+ (EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST),
+ 0, // TODO
+ 1,
+ "Loudness Enhancer",
+ "The Android Open Source Project",
+};
+
+enum le_state_e {
+ LOUDNESS_ENHANCER_STATE_UNINITIALIZED,
+ LOUDNESS_ENHANCER_STATE_INITIALIZED,
+ LOUDNESS_ENHANCER_STATE_ACTIVE,
+};
+
+struct LoudnessEnhancerContext {
+ const struct effect_interface_s *mItfe;
+ effect_config_t mConfig;
+ uint8_t mState;
+ int32_t mTargetGainmB;// target gain in mB
+ // in this implementation, there is no coupling between the compression on the left and right
+ // channels
+ le_fx::AdaptiveDynamicRangeCompression* mCompressor;
+};
+
+//
+//--- Local functions (not directly used by effect interface)
+//
+
+void LE_reset(LoudnessEnhancerContext *pContext)
+{
+ ALOGV(" > LE_reset(%p)", pContext);
+
+ if (pContext->mCompressor != NULL) {
+ float targetAmp = pow(10, pContext->mTargetGainmB/2000.0f); // mB to linear amplification
+ ALOGV("LE_reset(): Target gain=%dmB <=> factor=%.2fX", pContext->mTargetGainmB, targetAmp);
+ pContext->mCompressor->Initialize(targetAmp, pContext->mConfig.inputCfg.samplingRate);
+ } else {
+ ALOGE("LE_reset(%p): null compressors, can't apply target gain", pContext);
+ }
+}
+
+static inline int16_t clamp16(int32_t sample)
+{
+ if ((sample>>15) ^ (sample>>31))
+ sample = 0x7FFF ^ (sample>>31);
+ return sample;
+}
+
+//----------------------------------------------------------------------------
+// LE_setConfig()
+//----------------------------------------------------------------------------
+// Purpose: Set input and output audio configuration.
+//
+// Inputs:
+// pContext: effect engine context
+// pConfig: pointer to effect_config_t structure holding input and output
+// configuration parameters
+//
+// Outputs:
+//
+//----------------------------------------------------------------------------
+
+int LE_setConfig(LoudnessEnhancerContext *pContext, effect_config_t *pConfig)
+{
+ ALOGV("LE_setConfig(%p)", pContext);
+
+ if (pConfig->inputCfg.samplingRate != pConfig->outputCfg.samplingRate) return -EINVAL;
+ if (pConfig->inputCfg.channels != pConfig->outputCfg.channels) return -EINVAL;
+ if (pConfig->inputCfg.format != pConfig->outputCfg.format) return -EINVAL;
+ if (pConfig->inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) return -EINVAL;
+ if (pConfig->outputCfg.accessMode != EFFECT_BUFFER_ACCESS_WRITE &&
+ pConfig->outputCfg.accessMode != EFFECT_BUFFER_ACCESS_ACCUMULATE) return -EINVAL;
+ if (pConfig->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT) return -EINVAL;
+
+ pContext->mConfig = *pConfig;
+
+ LE_reset(pContext);
+
+ return 0;
+}
+
+
+//----------------------------------------------------------------------------
+// LE_getConfig()
+//----------------------------------------------------------------------------
+// Purpose: Get input and output audio configuration.
+//
+// Inputs:
+// pContext: effect engine context
+// pConfig: pointer to effect_config_t structure holding input and output
+// configuration parameters
+//
+// Outputs:
+//
+//----------------------------------------------------------------------------
+
+void LE_getConfig(LoudnessEnhancerContext *pContext, effect_config_t *pConfig)
+{
+ *pConfig = pContext->mConfig;
+}
+
+
+//----------------------------------------------------------------------------
+// LE_init()
+//----------------------------------------------------------------------------
+// Purpose: Initialize engine with default configuration.
+//
+// Inputs:
+// pContext: effect engine context
+//
+// Outputs:
+//
+//----------------------------------------------------------------------------
+
+int LE_init(LoudnessEnhancerContext *pContext)
+{
+ ALOGV("LE_init(%p)", pContext);
+
+ pContext->mConfig.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ;
+ pContext->mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+ pContext->mConfig.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
+ pContext->mConfig.inputCfg.samplingRate = 44100;
+ pContext->mConfig.inputCfg.bufferProvider.getBuffer = NULL;
+ pContext->mConfig.inputCfg.bufferProvider.releaseBuffer = NULL;
+ pContext->mConfig.inputCfg.bufferProvider.cookie = NULL;
+ pContext->mConfig.inputCfg.mask = EFFECT_CONFIG_ALL;
+ pContext->mConfig.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE;
+ pContext->mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+ pContext->mConfig.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
+ pContext->mConfig.outputCfg.samplingRate = 44100;
+ pContext->mConfig.outputCfg.bufferProvider.getBuffer = NULL;
+ pContext->mConfig.outputCfg.bufferProvider.releaseBuffer = NULL;
+ pContext->mConfig.outputCfg.bufferProvider.cookie = NULL;
+ pContext->mConfig.outputCfg.mask = EFFECT_CONFIG_ALL;
+
+ pContext->mTargetGainmB = LOUDNESS_ENHANCER_DEFAULT_TARGET_GAIN_MB;
+ float targetAmp = pow(10, pContext->mTargetGainmB/2000.0f); // mB to linear amplification
+ ALOGV("LE_init(): Target gain=%dmB <=> factor=%.2fX", pContext->mTargetGainmB, targetAmp);
+
+ if (pContext->mCompressor == NULL) {
+ pContext->mCompressor = new le_fx::AdaptiveDynamicRangeCompression();
+ pContext->mCompressor->Initialize(targetAmp, pContext->mConfig.inputCfg.samplingRate);
+ }
+
+ LE_setConfig(pContext, &pContext->mConfig);
+
+ return 0;
+}
+
+//
+//--- Effect Library Interface Implementation
+//
+
+int LELib_Create(const effect_uuid_t *uuid,
+ int32_t sessionId,
+ int32_t ioId,
+ effect_handle_t *pHandle) {
+ ALOGV("LELib_Create()");
+ int ret;
+ int i;
+
+ if (pHandle == NULL || uuid == NULL) {
+ return -EINVAL;
+ }
+
+ if (memcmp(uuid, &gLEDescriptor.uuid, sizeof(effect_uuid_t)) != 0) {
+ return -EINVAL;
+ }
+
+ LoudnessEnhancerContext *pContext = new LoudnessEnhancerContext;
+
+ pContext->mItfe = &gLEInterface;
+ pContext->mState = LOUDNESS_ENHANCER_STATE_UNINITIALIZED;
+
+ pContext->mCompressor = NULL;
+ ret = LE_init(pContext);
+ if (ret < 0) {
+ ALOGW("LELib_Create() init failed");
+ delete pContext;
+ return ret;
+ }
+
+ *pHandle = (effect_handle_t)pContext;
+
+ pContext->mState = LOUDNESS_ENHANCER_STATE_INITIALIZED;
+
+ ALOGV(" LELib_Create context is %p", pContext);
+
+ return 0;
+
+}
+
+int LELib_Release(effect_handle_t handle) {
+ LoudnessEnhancerContext * pContext = (LoudnessEnhancerContext *)handle;
+
+ ALOGV("LELib_Release %p", handle);
+ if (pContext == NULL) {
+ return -EINVAL;
+ }
+ pContext->mState = LOUDNESS_ENHANCER_STATE_UNINITIALIZED;
+ if (pContext->mCompressor != NULL) {
+ delete pContext->mCompressor;
+ pContext->mCompressor = NULL;
+ }
+ delete pContext;
+
+ return 0;
+}
+
+int LELib_GetDescriptor(const effect_uuid_t *uuid,
+ effect_descriptor_t *pDescriptor) {
+
+ if (pDescriptor == NULL || uuid == NULL){
+ ALOGV("LELib_GetDescriptor() called with NULL pointer");
+ return -EINVAL;
+ }
+
+ if (memcmp(uuid, &gLEDescriptor.uuid, sizeof(effect_uuid_t)) == 0) {
+ *pDescriptor = gLEDescriptor;
+ return 0;
+ }
+
+ return -EINVAL;
+} /* end LELib_GetDescriptor */
+
+//
+//--- Effect Control Interface Implementation
+//
+int LE_process(
+ effect_handle_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer)
+{
+ LoudnessEnhancerContext * pContext = (LoudnessEnhancerContext *)self;
+
+ if (pContext == NULL) {
+ return -EINVAL;
+ }
+
+ if (inBuffer == NULL || inBuffer->raw == NULL ||
+ outBuffer == NULL || outBuffer->raw == NULL ||
+ inBuffer->frameCount != outBuffer->frameCount ||
+ inBuffer->frameCount == 0) {
+ return -EINVAL;
+ }
+
+ //ALOGV("LE about to process %d samples", inBuffer->frameCount);
+ uint16_t inIdx;
+ float inputAmp = pow(10, pContext->mTargetGainmB/2000.0f);
+ float leftSample, rightSample;
+ for (inIdx = 0 ; inIdx < inBuffer->frameCount ; inIdx++) {
+ // makeup gain is applied on the input of the compressor
+ leftSample = inputAmp * (float)inBuffer->s16[2*inIdx];
+ rightSample = inputAmp * (float)inBuffer->s16[2*inIdx +1];
+ pContext->mCompressor->Compress(&leftSample, &rightSample);
+ inBuffer->s16[2*inIdx] = (int16_t) leftSample;
+ inBuffer->s16[2*inIdx +1] = (int16_t) rightSample;
+ }
+
+ if (inBuffer->raw != outBuffer->raw) {
+ if (pContext->mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) {
+ for (size_t i = 0; i < outBuffer->frameCount*2; i++) {
+ outBuffer->s16[i] = clamp16(outBuffer->s16[i] + inBuffer->s16[i]);
+ }
+ } else {
+ memcpy(outBuffer->raw, inBuffer->raw, outBuffer->frameCount * 2 * sizeof(int16_t));
+ }
+ }
+ if (pContext->mState != LOUDNESS_ENHANCER_STATE_ACTIVE) {
+ return -ENODATA;
+ }
+ return 0;
+}
+
+int LE_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
+ void *pCmdData, uint32_t *replySize, void *pReplyData) {
+
+ LoudnessEnhancerContext * pContext = (LoudnessEnhancerContext *)self;
+ int retsize;
+
+ if (pContext == NULL || pContext->mState == LOUDNESS_ENHANCER_STATE_UNINITIALIZED) {
+ return -EINVAL;
+ }
+
+// ALOGV("LE_command command %d cmdSize %d",cmdCode, cmdSize);
+ switch (cmdCode) {
+ case EFFECT_CMD_INIT:
+ if (pReplyData == NULL || *replySize != sizeof(int)) {
+ return -EINVAL;
+ }
+ *(int *) pReplyData = LE_init(pContext);
+ break;
+ case EFFECT_CMD_SET_CONFIG:
+ if (pCmdData == NULL || cmdSize != sizeof(effect_config_t)
+ || pReplyData == NULL || *replySize != sizeof(int)) {
+ return -EINVAL;
+ }
+ *(int *) pReplyData = LE_setConfig(pContext,
+ (effect_config_t *) pCmdData);
+ break;
+ case EFFECT_CMD_GET_CONFIG:
+ if (pReplyData == NULL ||
+ *replySize != sizeof(effect_config_t)) {
+ return -EINVAL;
+ }
+ LE_getConfig(pContext, (effect_config_t *)pReplyData);
+ break;
+ case EFFECT_CMD_RESET:
+ LE_reset(pContext);
+ break;
+ case EFFECT_CMD_ENABLE:
+ if (pReplyData == NULL || *replySize != sizeof(int)) {
+ return -EINVAL;
+ }
+ if (pContext->mState != LOUDNESS_ENHANCER_STATE_INITIALIZED) {
+ return -ENOSYS;
+ }
+ pContext->mState = LOUDNESS_ENHANCER_STATE_ACTIVE;
+ ALOGV("EFFECT_CMD_ENABLE() OK");
+ *(int *)pReplyData = 0;
+ break;
+ case EFFECT_CMD_DISABLE:
+ if (pReplyData == NULL || *replySize != sizeof(int)) {
+ return -EINVAL;
+ }
+ if (pContext->mState != LOUDNESS_ENHANCER_STATE_ACTIVE) {
+ return -ENOSYS;
+ }
+ pContext->mState = LOUDNESS_ENHANCER_STATE_INITIALIZED;
+ ALOGV("EFFECT_CMD_DISABLE() OK");
+ *(int *)pReplyData = 0;
+ break;
+ case EFFECT_CMD_GET_PARAM: {
+ if (pCmdData == NULL ||
+ cmdSize != (int)(sizeof(effect_param_t) + sizeof(uint32_t)) ||
+ pReplyData == NULL ||
+ *replySize < (int)(sizeof(effect_param_t) + sizeof(uint32_t) + sizeof(uint32_t))) {
+ return -EINVAL;
+ }
+ memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + sizeof(uint32_t));
+ effect_param_t *p = (effect_param_t *)pReplyData;
+ p->status = 0;
+ *replySize = sizeof(effect_param_t) + sizeof(uint32_t);
+ if (p->psize != sizeof(uint32_t)) {
+ p->status = -EINVAL;
+ break;
+ }
+ switch (*(uint32_t *)p->data) {
+ case LOUDNESS_ENHANCER_PARAM_TARGET_GAIN_MB:
+ ALOGV("get target gain(mB) = %d", pContext->mTargetGainmB);
+ *((int32_t *)p->data + 1) = pContext->mTargetGainmB;
+ p->vsize = sizeof(int32_t);
+ *replySize += sizeof(int32_t);
+ break;
+ default:
+ p->status = -EINVAL;
+ }
+ } break;
+ case EFFECT_CMD_SET_PARAM: {
+ if (pCmdData == NULL ||
+ cmdSize != (int)(sizeof(effect_param_t) + sizeof(uint32_t) + sizeof(uint32_t)) ||
+ pReplyData == NULL || *replySize != sizeof(int32_t)) {
+ return -EINVAL;
+ }
+ *(int32_t *)pReplyData = 0;
+ effect_param_t *p = (effect_param_t *)pCmdData;
+ if (p->psize != sizeof(uint32_t) || p->vsize != sizeof(uint32_t)) {
+ *(int32_t *)pReplyData = -EINVAL;
+ break;
+ }
+ switch (*(uint32_t *)p->data) {
+ case LOUDNESS_ENHANCER_PARAM_TARGET_GAIN_MB:
+ pContext->mTargetGainmB = *((int32_t *)p->data + 1);
+ ALOGV("set target gain(mB) = %d", pContext->mTargetGainmB);
+ LE_reset(pContext); // apply parameter update
+ break;
+ default:
+ *(int32_t *)pReplyData = -EINVAL;
+ }
+ } break;
+ case EFFECT_CMD_SET_DEVICE:
+ case EFFECT_CMD_SET_VOLUME:
+ case EFFECT_CMD_SET_AUDIO_MODE:
+ break;
+
+ default:
+ ALOGW("LE_command invalid command %d",cmdCode);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Effect Control Interface Implementation: get_descriptor */
+int LE_getDescriptor(effect_handle_t self,
+ effect_descriptor_t *pDescriptor)
+{
+ LoudnessEnhancerContext * pContext = (LoudnessEnhancerContext *) self;
+
+ if (pContext == NULL || pDescriptor == NULL) {
+ ALOGV("LE_getDescriptor() invalid param");
+ return -EINVAL;
+ }
+
+ *pDescriptor = gLEDescriptor;
+
+ return 0;
+} /* end LE_getDescriptor */
+
+// effect_handle_t interface implementation for DRC effect
+const struct effect_interface_s gLEInterface = {
+ LE_process,
+ LE_command,
+ LE_getDescriptor,
+ NULL,
+};
+
+// This is the only symbol that needs to be exported
+__attribute__ ((visibility ("default")))
+audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = {
+ tag : AUDIO_EFFECT_LIBRARY_TAG,
+ version : EFFECT_LIBRARY_API_VERSION,
+ name : "Loudness Enhancer Library",
+ implementor : "The Android Open Source Project",
+ create_effect : LELib_Create,
+ release_effect : LELib_Release,
+ get_descriptor : LELib_GetDescriptor,
+};
+
+}; // extern "C"
+
diff --git a/media/libeffects/loudness/MODULE_LICENSE_APACHE2 b/media/libeffects/loudness/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media/libeffects/loudness/MODULE_LICENSE_APACHE2
diff --git a/media/libeffects/loudness/NOTICE b/media/libeffects/loudness/NOTICE
new file mode 100644
index 0000000..ad6ed94
--- /dev/null
+++ b/media/libeffects/loudness/NOTICE
@@ -0,0 +1,190 @@
+
+ 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/media/libeffects/loudness/common/core/basic_types.h b/media/libeffects/loudness/common/core/basic_types.h
new file mode 100644
index 0000000..593e914
--- /dev/null
+++ b/media/libeffects/loudness/common/core/basic_types.h
@@ -0,0 +1,114 @@
+/*
+ * 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 LE_FX_ENGINE_COMMON_CORE_BASIC_TYPES_H_
+#define LE_FX_ENGINE_COMMON_CORE_BASIC_TYPES_H_
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string>
+using ::std::string;
+using ::std::basic_string;
+#include <vector>
+using ::std::vector;
+
+#include "common/core/os.h"
+
+// -----------------------------------------------------------------------------
+// Definitions of common basic types:
+// -----------------------------------------------------------------------------
+
+#if !defined(G_COMPILE) && !defined(BASE_INTEGRAL_TYPES_H_)
+
+namespace le_fx {
+
+typedef signed char schar;
+typedef signed char int8;
+typedef short int16;
+typedef int int32;
+typedef long long int64;
+
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef unsigned int uint32;
+typedef unsigned long long uint64;
+
+} // namespace le_fx
+
+#endif
+
+namespace le_fx {
+
+struct FloatArray {
+ int length;
+ float *data;
+
+ FloatArray(void) {
+ data = NULL;
+ length = 0;
+ }
+};
+
+struct Int16Array {
+ int length;
+ int16 *data;
+
+ Int16Array(void) {
+ data = NULL;
+ length = 0;
+ }
+};
+
+struct Int32Array {
+ int length;
+ int32 *data;
+
+ Int32Array(void) {
+ data = NULL;
+ length = 0;
+ }
+};
+
+struct Int8Array {
+ int length;
+ uint8 *data;
+
+ Int8Array(void) {
+ data = NULL;
+ length = 0;
+ }
+};
+
+//
+// Simple wrapper for waveform data:
+//
+class WaveData : public vector<int16> {
+ public:
+ WaveData();
+ ~WaveData();
+
+ void Set(int number_samples, int sampling_rate, int16 *data);
+ int sample_rate(void) const;
+ void set_sample_rate(int sample_rate);
+ bool Equals(const WaveData &wave_data, int threshold = 0) const;
+
+ private:
+ int sample_rate_;
+};
+
+} // namespace le_fx
+
+#endif // LE_FX_ENGINE_COMMON_CORE_BASIC_TYPES_H_
diff --git a/media/libeffects/loudness/common/core/byte_swapper.h b/media/libeffects/loudness/common/core/byte_swapper.h
new file mode 100644
index 0000000..8f0caf3
--- /dev/null
+++ b/media/libeffects/loudness/common/core/byte_swapper.h
@@ -0,0 +1,151 @@
+/*
+ * 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 LE_FX_ENGINE_COMMON_CORE_BYTE_SWAPPER_H_
+#define LE_FX_ENGINE_COMMON_CORE_BYTE_SWAPPER_H_
+
+#include <stdio.h>
+#include <string.h>
+
+#include "common/core/basic_types.h"
+#include "common/core/os.h"
+
+namespace le_fx {
+
+namespace arch {
+
+inline bool IsLittleEndian(void) {
+ int16 word = 1;
+ char *cp = reinterpret_cast<char *>(&word);
+ return cp[0] != 0;
+}
+
+inline bool IsBigEndian(void) {
+ return !IsLittleEndian();
+}
+
+template <typename T, unsigned int kValSize>
+struct ByteSwapper {
+ static T Swap(const T &val) {
+ T new_val = val;
+ char *first = &new_val, *last = first + kValSize - 1, x;
+ for (; first < last; ++first, --last) {
+ x = *last;
+ *last = *first;
+ *first = x;
+ }
+ return new_val;
+ }
+};
+
+template <typename T>
+struct ByteSwapper<T, 1> {
+ static T Swap(const T &val) {
+ return val;
+ }
+};
+
+template <typename T>
+struct ByteSwapper<T, 2> {
+ static T Swap(const T &val) {
+ T new_val;
+ const char *o = (const char *)&val;
+ char *p = reinterpret_cast<char *>(&new_val);
+ p[0] = o[1];
+ p[1] = o[0];
+ return new_val;
+ }
+};
+
+template <typename T>
+struct ByteSwapper<T, 4> {
+ static T Swap(const T &val) {
+ T new_val;
+ const char *o = (const char *)&val;
+ char *p = reinterpret_cast<char *>(&new_val);
+ p[0] = o[3];
+ p[1] = o[2];
+ p[2] = o[1];
+ p[3] = o[0];
+ return new_val;
+ }
+};
+
+template <typename T>
+struct ByteSwapper<T, 8> {
+ static T Swap(const T &val) {
+ T new_val = val;
+ const char *o = (const char *)&val;
+ char *p = reinterpret_cast<char *>(&new_val);
+ p[0] = o[7];
+ p[1] = o[6];
+ p[2] = o[5];
+ p[3] = o[4];
+ p[4] = o[3];
+ p[5] = o[2];
+ p[6] = o[1];
+ p[7] = o[0];
+ return new_val;
+ }
+};
+
+template <typename T>
+T SwapBytes(const T &val, bool force_swap) {
+ if (force_swap) {
+#if !defined(LE_FX__NEED_BYTESWAP)
+ return ByteSwapper<T, sizeof(T)>::Swap(val);
+#else
+ return val;
+#endif // !LE_FX_NEED_BYTESWAP
+ } else {
+#if !defined(LE_FX_NEED_BYTESWAP)
+ return val;
+#else
+ return ByteSwapper<T, sizeof(T)>::Swap(val);
+#endif // !LE_FX_NEED_BYTESWAP
+ }
+}
+
+template <typename T>
+const T *SwapBytes(const T *vals, unsigned int num_items, bool force_swap) {
+ if (force_swap) {
+#if !defined(LE_FX_NEED_BYTESWAP)
+ T *writeable_vals = const_cast<T *>(vals);
+ for (unsigned int i = 0; i < num_items; i++) {
+ writeable_vals[i] = ByteSwapper<T, sizeof(T)>::Swap(vals[i]);
+ }
+ return writeable_vals;
+#else
+ return vals;
+#endif // !LE_FX_NEED_BYTESWAP
+ } else {
+#if !defined(LE_FX_NEED_BYTESWAP)
+ return vals;
+#else
+ T *writeable_vals = const_cast<T *>(vals);
+ for (unsigned int i = 0; i < num_items; i++) {
+ writeable_vals[i] = ByteSwapper<T, sizeof(T)>::Swap(vals[i]);
+ }
+ return writeable_vals;
+#endif // !LE_FX_NEED_BYTESWAP
+ }
+}
+
+} // namespace arch
+
+} // namespace le_fx
+
+#endif // LE_FX_ENGINE_COMMON_CORE_BYTE_SWAPPER_H_
diff --git a/media/libeffects/loudness/common/core/math.h b/media/libeffects/loudness/common/core/math.h
new file mode 100644
index 0000000..3f302cc
--- /dev/null
+++ b/media/libeffects/loudness/common/core/math.h
@@ -0,0 +1,89 @@
+/*
+ * 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 LE_FX_ENGINE_COMMON_CORE_MATH_H_
+#define LE_FX_ENGINE_COMMON_CORE_MATH_H_
+
+#include <math.h>
+#include <algorithm>
+using ::std::min;
+using ::std::max;
+using ::std::fill;
+using ::std::fill_n;using ::std::lower_bound;
+#include <cmath>
+#include <math.h>
+//using ::std::fpclassify;
+
+#include "common/core/os.h"
+#include "common/core/types.h"
+
+namespace le_fx {
+namespace math {
+
+// A fast approximation to log2(.)
+inline float fast_log2(float val) {
+ int* const exp_ptr = reinterpret_cast <int *> (&val);
+ int x = *exp_ptr;
+ const int log_2 = ((x >> 23) & 255) - 128;
+ x &= ~(255 << 23);
+ x += 127 << 23;
+ *exp_ptr = x;
+ val = ((-1.0f / 3) * val + 2) * val - 2.0f / 3;
+ return static_cast<float>(val + log_2);
+}
+
+// A fast approximation to log(.)
+inline float fast_log(float val) {
+ return fast_log2(val) *
+ 0.693147180559945286226763982995180413126945495605468750f;
+}
+
+// An approximation of the exp(.) function using a 5-th order Taylor expansion.
+// It's pretty accurate between +-0.1 and accurate to 10e-3 between +-1
+template <typename T>
+inline T ExpApproximationViaTaylorExpansionOrder5(T x) {
+ const T x2 = x * x;
+ const T x3 = x2 * x;
+ const T x4 = x2 * x2;
+ const T x5 = x3 * x2;
+ return 1.0f + x + 0.5f * x2 +
+ 0.16666666666666665741480812812369549646973609924316406250f * x3 +
+ 0.0416666666666666643537020320309238741174340248107910156250f * x4 +
+ 0.008333333333333333217685101601546193705871701240539550781250f * x5;
+}
+
+} // namespace math
+} // namespace le_fx
+
+// Math functions missing in Android NDK:
+#if defined(LE_FX_OS_ANDROID)
+
+namespace std {
+
+//
+// Round to the nearest integer: We need this implementation
+// since std::round is missing on android.
+//
+template <typename T>
+inline T round(const T &x) {
+ return static_cast<T>(std::floor(static_cast<double>(x) + 0.5));
+}
+
+} // namespace std
+
+#endif // LE_FX_OS_ANDROID
+
+#endif // LE_FX_ENGINE_COMMON_CORE_MATH_H_
diff --git a/media/libeffects/loudness/common/core/os.h b/media/libeffects/loudness/common/core/os.h
new file mode 100644
index 0000000..4a8ce82
--- /dev/null
+++ b/media/libeffects/loudness/common/core/os.h
@@ -0,0 +1,29 @@
+/*
+ * 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 LE_FX_ENGINE_COMMON_CORE_OS_H_
+#define LE_FX_ENGINE_COMMON_CORE_OS_H_
+
+// -----------------------------------------------------------------------------
+// OS Identification:
+// -----------------------------------------------------------------------------
+
+#define LE_FX_OS_UNIX
+#if defined(__ANDROID__)
+# define LE_FX_OS_ANDROID
+#endif // Android
+
+#endif // LE_FX_ENGINE_COMMON_CORE_OS_H_
diff --git a/media/libeffects/loudness/common/core/types.h b/media/libeffects/loudness/common/core/types.h
new file mode 100644
index 0000000..d1b6c6a
--- /dev/null
+++ b/media/libeffects/loudness/common/core/types.h
@@ -0,0 +1,31 @@
+/*
+ * 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 LE_FX_ENGINE_COMMON_CORE_TYPES_H_
+#define LE_FX_ENGINE_COMMON_CORE_TYPES_H_
+
+#include "common/core/os.h"
+
+#include "common/core/basic_types.h"
+
+#ifndef LE_FX_DISALLOW_COPY_AND_ASSIGN
+#define LE_FX_DISALLOW_COPY_AND_ASSIGN(TypeName) \
+ TypeName(const TypeName&); \
+ void operator=(const TypeName&)
+#endif // LE_FX_DISALLOW_COPY_AND_ASSIGN
+
+
+#endif // LE_FX_ENGINE_COMMON_CORE_TYPES_H_
diff --git a/media/libeffects/loudness/dsp/core/basic-inl.h b/media/libeffects/loudness/dsp/core/basic-inl.h
new file mode 100644
index 0000000..3f77147
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/basic-inl.h
@@ -0,0 +1,48 @@
+/*
+ * 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 LE_FX_ENGINE_DSP_CORE_BASIC_INL_H_
+#define LE_FX_ENGINE_DSP_CORE_BASIC_INL_H_
+
+#include <math.h>
+
+namespace le_fx {
+
+namespace sigmod {
+
+template <typename T>
+int SearchIndex(const T x_data[],
+ T x,
+ int start_index,
+ int end_index) {
+ int start = start_index;
+ int end = end_index;
+ while (end > start + 1) {
+ int i = (end + start) / 2;
+ if (x_data[i] > x) {
+ end = i;
+ } else {
+ start = i;
+ }
+ }
+ return start;
+}
+
+} // namespace sigmod
+
+} // namespace le_fx
+
+#endif // LE_FX_ENGINE_DSP_CORE_BASIC_INL_H_
diff --git a/media/libeffects/loudness/dsp/core/basic.h b/media/libeffects/loudness/dsp/core/basic.h
new file mode 100644
index 0000000..27e0a8d
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/basic.h
@@ -0,0 +1,48 @@
+/*
+ * 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 LE_FX_ENGINE_DSP_CORE_BASIC_H_
+#define LE_FX_ENGINE_DSP_CORE_BASIC_H_
+
+#include <limits.h>
+#include "common/core/math.h"
+#include "common/core/types.h"
+
+namespace le_fx {
+
+namespace sigmod {
+
+// Searchs for the interval that contains <x> using a divide-and-conquer
+// algorithm.
+// X[]: a vector of sorted values (X[i+1] > X[i])
+// x: a value
+// StartIndex: the minimum searched index
+// EndIndex: the maximum searched index
+// returns: the index <i> that satisfies: X[i] <= x <= X[i+1] &&
+// StartIndex <= i <= (EndIndex-1)
+template <typename T>
+int SearchIndex(const T x_data[],
+ T x,
+ int start_index,
+ int end_index);
+
+} // namespace sigmod
+
+} // namespace le_fx
+
+#include "dsp/core/basic-inl.h"
+
+#endif // LE_FX_ENGINE_DSP_CORE_BASIC_H_
diff --git a/media/libeffects/loudness/dsp/core/dynamic_range_compression-inl.h b/media/libeffects/loudness/dsp/core/dynamic_range_compression-inl.h
new file mode 100644
index 0000000..da75ceb
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/dynamic_range_compression-inl.h
@@ -0,0 +1,45 @@
+/*
+ * 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 LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_INL_H_
+#define LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_INL_H_
+
+//#define LOG_NDEBUG 0
+#include <cutils/log.h>
+
+
+namespace le_fx {
+
+
+inline void AdaptiveDynamicRangeCompression::set_knee_threshold(float decibel) {
+ // Converts to 1og-base
+ knee_threshold_in_decibel_ = decibel;
+ knee_threshold_ = 0.1151292546497023061569109358970308676362037658691406250f *
+ decibel + 10.39717719035538401328722102334722876548767089843750f;
+}
+
+
+inline void AdaptiveDynamicRangeCompression::set_knee_threshold_via_target_gain(
+ float target_gain) {
+ const float decibel = target_gain_to_knee_threshold_.Interpolate(
+ target_gain);
+ ALOGV("set_knee_threshold_via_target_gain: decibel =%.3fdB", decibel);
+ set_knee_threshold(decibel);
+}
+
+} // namespace le_fx
+
+
+#endif // LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_INL_H_
diff --git a/media/libeffects/loudness/dsp/core/dynamic_range_compression.cpp b/media/libeffects/loudness/dsp/core/dynamic_range_compression.cpp
new file mode 100644
index 0000000..7bd068e
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/dynamic_range_compression.cpp
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#include <cmath>
+
+#include "common/core/math.h"
+#include "common/core/types.h"
+#include "dsp/core/basic.h"
+#include "dsp/core/interpolation.h"
+#include "dsp/core/dynamic_range_compression.h"
+
+//#define LOG_NDEBUG 0
+#include <cutils/log.h>
+
+
+namespace le_fx {
+
+// Definitions for static const class members declared in
+// dynamic_range_compression.h.
+const float AdaptiveDynamicRangeCompression::kMinAbsValue = 0.000001f;
+const float AdaptiveDynamicRangeCompression::kMinLogAbsValue =
+ 0.032766999999999997517097227728299912996590137481689453125f;
+const float AdaptiveDynamicRangeCompression::kFixedPointLimit = 32767.0f;
+const float AdaptiveDynamicRangeCompression::kInverseFixedPointLimit =
+ 1.0f / AdaptiveDynamicRangeCompression::kFixedPointLimit;
+const float AdaptiveDynamicRangeCompression::kDefaultKneeThresholdInDecibel =
+ -8.0f;
+const float AdaptiveDynamicRangeCompression::kCompressionRatio = 7.0f;
+const float AdaptiveDynamicRangeCompression::kTauAttack = 0.001f;
+const float AdaptiveDynamicRangeCompression::kTauRelease = 0.015f;
+
+AdaptiveDynamicRangeCompression::AdaptiveDynamicRangeCompression() {
+ static const float kTargetGain[] = {
+ 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
+ static const float kKneeThreshold[] = {
+ -8.0f, -8.0f, -8.5f, -9.0f, -10.0f };
+ target_gain_to_knee_threshold_.Initialize(
+ &kTargetGain[0], &kKneeThreshold[0],
+ sizeof(kTargetGain) / sizeof(kTargetGain[0]));
+}
+
+bool AdaptiveDynamicRangeCompression::Initialize(
+ float target_gain, float sampling_rate) {
+ set_knee_threshold_via_target_gain(target_gain);
+ sampling_rate_ = sampling_rate;
+ state_ = 0.0f;
+ compressor_gain_ = 1.0f;
+ if (kTauAttack > 0.0f) {
+ const float taufs = kTauAttack * sampling_rate_;
+ alpha_attack_ = std::exp(-1.0f / taufs);
+ } else {
+ alpha_attack_ = 0.0f;
+ }
+ if (kTauRelease > 0.0f) {
+ const float taufs = kTauRelease * sampling_rate_;
+ alpha_release_ = std::exp(-1.0f / taufs);
+ } else {
+ alpha_release_ = 0.0f;
+ }
+ // Feed-forward topology
+ slope_ = 1.0f / kCompressionRatio - 1.0f;
+ return true;
+}
+
+float AdaptiveDynamicRangeCompression::Compress(float x) {
+ const float max_abs_x = std::max(std::fabs(x), kMinLogAbsValue);
+ const float max_abs_x_dB = math::fast_log(max_abs_x);
+ // Subtract Threshold from log-encoded input to get the amount of overshoot
+ const float overshoot = max_abs_x_dB - knee_threshold_;
+ // Hard half-wave rectifier
+ const float rect = std::max(overshoot, 0.0f);
+ // Multiply rectified overshoot with slope
+ const float cv = rect * slope_;
+ const float prev_state = state_;
+ if (cv <= state_) {
+ state_ = alpha_attack_ * state_ + (1.0f - alpha_attack_) * cv;
+ } else {
+ state_ = alpha_release_ * state_ + (1.0f - alpha_release_) * cv;
+ }
+ compressor_gain_ *=
+ math::ExpApproximationViaTaylorExpansionOrder5(state_ - prev_state);
+ x *= compressor_gain_;
+ if (x > kFixedPointLimit) {
+ return kFixedPointLimit;
+ }
+ if (x < -kFixedPointLimit) {
+ return -kFixedPointLimit;
+ }
+ return x;
+}
+
+void AdaptiveDynamicRangeCompression::Compress(float *x1, float *x2) {
+ // Taking the maximum amplitude of both channels
+ const float max_abs_x = std::max(std::fabs(*x1),
+ std::max(std::fabs(*x2), kMinLogAbsValue));
+ const float max_abs_x_dB = math::fast_log(max_abs_x);
+ // Subtract Threshold from log-encoded input to get the amount of overshoot
+ const float overshoot = max_abs_x_dB - knee_threshold_;
+ // Hard half-wave rectifier
+ const float rect = std::max(overshoot, 0.0f);
+ // Multiply rectified overshoot with slope
+ const float cv = rect * slope_;
+ const float prev_state = state_;
+ if (cv <= state_) {
+ state_ = alpha_attack_ * state_ + (1.0f - alpha_attack_) * cv;
+ } else {
+ state_ = alpha_release_ * state_ + (1.0f - alpha_release_) * cv;
+ }
+ compressor_gain_ *=
+ math::ExpApproximationViaTaylorExpansionOrder5(state_ - prev_state);
+ *x1 *= compressor_gain_;
+ if (*x1 > kFixedPointLimit) {
+ *x1 = kFixedPointLimit;
+ }
+ if (*x1 < -kFixedPointLimit) {
+ *x1 = -kFixedPointLimit;
+ }
+ *x2 *= compressor_gain_;
+ if (*x2 > kFixedPointLimit) {
+ *x2 = kFixedPointLimit;
+ }
+ if (*x2 < -kFixedPointLimit) {
+ *x2 = -kFixedPointLimit;
+ }
+}
+
+} // namespace le_fx
+
diff --git a/media/libeffects/loudness/dsp/core/dynamic_range_compression.h b/media/libeffects/loudness/dsp/core/dynamic_range_compression.h
new file mode 100644
index 0000000..2821a78
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/dynamic_range_compression.h
@@ -0,0 +1,119 @@
+/*
+ * 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 LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_H_
+#define LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_H_
+
+#include "common/core/types.h"
+#include "common/core/math.h"
+#include "dsp/core/basic.h"
+#include "dsp/core/interpolation.h"
+
+//#define LOG_NDEBUG 0
+#include <cutils/log.h>
+
+
+namespace le_fx {
+
+// An adaptive dynamic range compression algorithm. The gain adaptation is made
+// at the logarithmic domain and it is based on a Branching-Smooth compensated
+// digital peak detector with different time constants for attack and release.
+class AdaptiveDynamicRangeCompression {
+ public:
+ AdaptiveDynamicRangeCompression();
+
+ // Initializes the compressor using prior information. It assumes that the
+ // input signal is speech from high-quality recordings that is scaled and then
+ // fed to the compressor. The compressor is tuned according to the target gain
+ // that is expected to be applied.
+ //
+ // Target gain receives values between 0.0 and 10.0. The knee threshold is
+ // reduced as the target gain increases in order to fit the increased range of
+ // values.
+ //
+ // Values between 1.0 and 2.0 will only mildly affect your signal. Higher
+ // values will reduce the dynamic range of the signal to the benefit of
+ // increased loudness.
+ //
+ // If nothing is known regarding the input, a `target_gain` of 1.0f is a
+ // relatively safe choice for many signals.
+ bool Initialize(float target_gain, float sampling_rate);
+
+ // A fast version of the algorithm that uses approximate computations for the
+ // log(.) and exp(.).
+ float Compress(float x);
+
+ // Stereo channel version of the compressor
+ void Compress(float *x1, float *x2);
+
+ // This version is slower than Compress(.) but faster than CompressSlow(.)
+ float CompressNormalSpeed(float x);
+
+ // A slow version of the algorithm that is easier for further developement,
+ // tuning and debugging
+ float CompressSlow(float x);
+
+ // Sets knee threshold (in decibel).
+ void set_knee_threshold(float decibel);
+
+ // Sets knee threshold via the target gain using an experimentally derived
+ // relationship.
+ void set_knee_threshold_via_target_gain(float target_gain);
+
+ private:
+ // The minimum accepted absolute input value and it's natural logarithm. This
+ // is to prevent numerical issues when the input is close to zero
+ static const float kMinAbsValue;
+ static const float kMinLogAbsValue;
+ // Fixed-point arithmetic limits
+ static const float kFixedPointLimit;
+ static const float kInverseFixedPointLimit;
+ // The default knee threshold in decibel. The knee threshold defines when the
+ // compressor is actually starting to compress the value of the input samples
+ static const float kDefaultKneeThresholdInDecibel;
+ // The compression ratio is the reciprocal of the slope of the line segment
+ // above the threshold (in the log-domain). The ratio controls the
+ // effectiveness of the compression.
+ static const float kCompressionRatio;
+ // The attack time of the envelope detector
+ static const float kTauAttack;
+ // The release time of the envelope detector
+ static const float kTauRelease;
+
+ float sampling_rate_;
+ // the internal state of the envelope detector
+ float state_;
+ // the latest gain factor that was applied to the input signal
+ float compressor_gain_;
+ // attack constant for exponential dumping
+ float alpha_attack_;
+ // release constant for exponential dumping
+ float alpha_release_;
+ float slope_;
+ // The knee threshold
+ float knee_threshold_;
+ float knee_threshold_in_decibel_;
+ // This interpolator provides the function that relates target gain to knee
+ // threshold.
+ sigmod::InterpolatorLinear<float> target_gain_to_knee_threshold_;
+
+ LE_FX_DISALLOW_COPY_AND_ASSIGN(AdaptiveDynamicRangeCompression);
+};
+
+} // namespace le_fx
+
+#include "dsp/core/dynamic_range_compression-inl.h"
+
+#endif // LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_H_
diff --git a/media/libeffects/loudness/dsp/core/interpolation.h b/media/libeffects/loudness/dsp/core/interpolation.h
new file mode 100644
index 0000000..23c287c
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/interpolation.h
@@ -0,0 +1,24 @@
+/*
+ * 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 LE_FX_ENGINE_DSP_CORE_INTERPOLATION_H_
+#define LE_FX_ENGINE_DSP_CORE_INTERPOLATION_H_
+
+#include "common/core/math.h"
+#include "dsp/core/interpolator_base.h"
+#include "dsp/core/interpolator_linear.h"
+
+#endif // LE_FX_ENGINE_DSP_CORE_INTERPOLATION_H_
+
diff --git a/media/libeffects/loudness/dsp/core/interpolator_base-inl.h b/media/libeffects/loudness/dsp/core/interpolator_base-inl.h
new file mode 100644
index 0000000..bd08b65
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/interpolator_base-inl.h
@@ -0,0 +1,180 @@
+/*
+ * 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 LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_INL_H_
+#define LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_INL_H_
+
+#include "dsp/core/basic.h"
+
+//#define LOG_NDEBUG 0
+#include <cutils/log.h>
+
+
+namespace le_fx {
+
+namespace sigmod {
+
+template <typename T, class Algorithm>
+InterpolatorBase<T, Algorithm>::InterpolatorBase() {
+ status_ = false;
+ cached_index_ = 0;
+ x_data_ = NULL;
+ y_data_ = NULL;
+ data_length_ = 0;
+ own_x_data_ = false;
+ x_start_offset_ = 0.0;
+ last_element_index_ = -1;
+ x_inverse_sampling_interval_ = 0.0;
+ state_ = NULL;
+}
+
+template <typename T, class Algorithm>
+InterpolatorBase<T, Algorithm>::~InterpolatorBase() {
+ delete [] state_;
+ if (own_x_data_) {
+ delete [] x_data_;
+ }
+}
+
+template <typename T, class Algorithm>
+bool InterpolatorBase<T, Algorithm>::Initialize(const vector<T> &x_data,
+ const vector<T> &y_data) {
+#ifndef NDEBUG
+ if (x_data.size() != y_data.size()) {
+ LoggerError("InterpolatorBase::Initialize: xData size (%d) != yData size"
+ " (%d)", x_data.size(), y_data.size());
+ }
+#endif
+ return Initialize(&x_data[0], &y_data[0], x_data.size());
+}
+
+template <typename T, class Algorithm>
+bool InterpolatorBase<T, Algorithm>::Initialize(double x_start_offset,
+ double x_sampling_interval,
+ const vector<T> &y_data) {
+ return Initialize(x_start_offset,
+ x_sampling_interval,
+ &y_data[0],
+ y_data.size());
+}
+
+template <typename T, class Algorithm>
+bool InterpolatorBase<T, Algorithm>::Initialize(double x_start_offset,
+ double x_sampling_interval,
+ const T *y_data,
+ int data_length) {
+ // Constructs and populate x-axis data: `x_data_`
+ T *x_data_tmp = new T[data_length];
+ float time_offset = x_start_offset;
+ for (int n = 0; n < data_length; n++) {
+ x_data_tmp[n] = time_offset;
+ time_offset += x_sampling_interval;
+ }
+ Initialize(x_data_tmp, y_data, data_length);
+ // Sets-up the regularly sampled interpolation mode
+ x_start_offset_ = x_start_offset;
+ x_inverse_sampling_interval_ = 1.0 / x_sampling_interval;
+ own_x_data_ = true;
+ return status_;
+}
+
+
+template <typename T, class Algorithm>
+bool InterpolatorBase<T, Algorithm>::Initialize(
+ const T *x_data, const T *y_data, int data_length) {
+ // Default settings
+ cached_index_ = 0;
+ data_length_ = 0;
+ x_start_offset_ = 0;
+ x_inverse_sampling_interval_ = 0;
+ state_ = NULL;
+ // Input data is externally owned
+ own_x_data_ = false;
+ x_data_ = x_data;
+ y_data_ = y_data;
+ data_length_ = data_length;
+ last_element_index_ = data_length - 1;
+ // Check input data sanity
+ for (int n = 0; n < last_element_index_; ++n) {
+ if (x_data_[n + 1] <= x_data_[n]) {
+ ALOGE("InterpolatorBase::Initialize: xData are not ordered or "
+ "contain equal values (X[%d] <= X[%d]) (%.5e <= %.5e)",
+ n + 1, n, x_data_[n + 1], x_data_[n]);
+ status_ = false;
+ return false;
+ }
+ }
+ // Pre-compute internal state by calling the corresponding function of the
+ // derived class.
+ status_ = static_cast<Algorithm*>(this)->SetInternalState();
+ return status_;
+}
+
+template <typename T, class Algorithm>
+T InterpolatorBase<T, Algorithm>::Interpolate(T x) {
+#ifndef NDEBUG
+ if (cached_index_ < 0 || cached_index_ > data_length_ - 2) {
+ LoggerError("InterpolatorBase:Interpolate: CachedIndex_ out of bounds "
+ "[0, %d, %d]", cached_index_, data_length_ - 2);
+ }
+#endif
+ // Search for the containing interval
+ if (x <= x_data_[cached_index_]) {
+ if (cached_index_ <= 0) {
+ cached_index_ = 0;
+ return y_data_[0];
+ }
+ if (x >= x_data_[cached_index_ - 1]) {
+ cached_index_--; // Fast descending
+ } else {
+ if (x <= x_data_[0]) {
+ cached_index_ = 0;
+ return y_data_[0];
+ }
+ cached_index_ = SearchIndex(x_data_, x, 0, cached_index_);
+ }
+ } else {
+ if (cached_index_ >= last_element_index_) {
+ cached_index_ = last_element_index_;
+ return y_data_[last_element_index_];
+ }
+ if (x > x_data_[cached_index_ + 1]) {
+ if (cached_index_ + 2 > last_element_index_) {
+ cached_index_ = last_element_index_ - 1;
+ return y_data_[last_element_index_];
+ }
+ if (x <= x_data_[cached_index_ + 2]) {
+ cached_index_++; // Fast ascending
+ } else {
+ if (x >= x_data_[last_element_index_]) {
+ cached_index_ = last_element_index_ - 1;
+ return y_data_[last_element_index_];
+ }
+ cached_index_ = SearchIndex(
+ x_data_, x, cached_index_, last_element_index_);
+ }
+ }
+ }
+ // Compute interpolated value by calling the corresponding function of the
+ // derived class.
+ return static_cast<Algorithm*>(this)->MethodSpecificInterpolation(x);
+}
+
+} // namespace sigmod
+
+} // namespace le_fx
+
+#endif // LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_INL_H_
diff --git a/media/libeffects/loudness/dsp/core/interpolator_base.h b/media/libeffects/loudness/dsp/core/interpolator_base.h
new file mode 100644
index 0000000..0cd1a35
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/interpolator_base.h
@@ -0,0 +1,112 @@
+/*
+ * 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 LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_H_
+#define LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_H_
+
+#include "common/core/types.h"
+
+namespace le_fx {
+
+namespace sigmod {
+
+// Interpolation base-class that provides the interface, while it is the derived
+// class that provides the specific interpolation algorithm. The following list
+// of interpolation algorithms are currently present:
+//
+// InterpolationSine<T>: weighted interpolation between y_data[n] and
+// y_data[n+1] using a sin(.) weighting factor from
+// 0 to pi/4.
+// InterpolationLinear<T>: linear interpolation
+// InterpolationSplines<T>: spline-based interpolation
+//
+// Example (using derived spline-based interpolation class):
+// InterpolatorSplines<float> interp(x_data, y_data, data_length);
+// for (int n = 0; n < data_length; n++) Y[n] = interp.Interpolate(X[n]);
+//
+template <typename T, class Algorithm>
+class InterpolatorBase {
+ public:
+ InterpolatorBase();
+ ~InterpolatorBase();
+
+ // Generic random-access interpolation with arbitrary spaced x-axis samples.
+ // Below X[0], the interpolator returns Y[0]. Above X[data_length-1], it
+ // returns Y[data_length-1].
+ T Interpolate(T x);
+
+ bool get_status() const {
+ return status_;
+ }
+
+ // Initializes internal buffers.
+ // x_data: [(data_length)x1] x-axis coordinates (searching axis)
+ // y_data: [(data_length)x1] y-axis coordinates (interpolation axis)
+ // data_length: number of points
+ // returns `true` if everything is ok, `false`, otherwise
+ bool Initialize(const T *x_data, const T *y_data, int data_length);
+
+ // Initializes internal buffers.
+ // x_data: x-axis coordinates (searching axis)
+ // y_data: y-axis coordinates (interpolating axis)
+ // returns `true` if everything is ok, `false`, otherwise
+ bool Initialize(const vector<T> &x_data, const vector<T> &y_data);
+
+ // Initialization for regularly sampled sequences, where:
+ // x_data[i] = x_start_offset + i * x_sampling_interval
+ bool Initialize(double x_start_offset,
+ double x_sampling_interval,
+ const vector<T> &y_data);
+
+ // Initialization for regularly sampled sequences, where:
+ // x_data[i] = x_start_offset + i * x_sampling_interval
+ bool Initialize(double x_start_offset,
+ double x_sampling_interval,
+ const T *y_data,
+ int data_length);
+
+ protected:
+ // Is set to false if something goes wrong, and to true if everything is ok.
+ bool status_;
+
+ // The start-index of the previously searched interval
+ int cached_index_;
+
+ // Data points
+ const T *x_data_; // Externally or internally owned, depending on own_x_data_
+ const T *y_data_; // Externally owned (always)
+ int data_length_;
+ // Index of the last element `data_length_ - 1` kept here for optimization
+ int last_element_index_;
+ bool own_x_data_;
+ // For regularly-samples sequences, keep only the boundaries and the intervals
+ T x_start_offset_;
+ float x_inverse_sampling_interval_;
+
+ // Algorithm state (internally owned)
+ double *state_;
+
+ private:
+ LE_FX_DISALLOW_COPY_AND_ASSIGN(InterpolatorBase);
+};
+
+} // namespace sigmod
+
+} // namespace le_fx
+
+#include "dsp/core/interpolator_base-inl.h"
+
+#endif // LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_H_
diff --git a/media/libeffects/loudness/dsp/core/interpolator_linear.h b/media/libeffects/loudness/dsp/core/interpolator_linear.h
new file mode 100644
index 0000000..434698a
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/interpolator_linear.h
@@ -0,0 +1,81 @@
+/*
+ * 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 LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_LINEAR_H_
+#define LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_LINEAR_H_
+
+#include <math.h>
+#include "dsp/core/interpolator_base.h"
+
+namespace le_fx {
+
+namespace sigmod {
+
+// Linear interpolation class.
+//
+// The main functionality of this class is provided by it's base-class, so
+// please refer to: InterpolatorBase
+//
+// Example:
+// InterpolatorLinear<float> interp(x_data, y_data, data_length);
+// for (int n = 0; n < data_length; n++) Y[n] = interp.Interpolate(X[n]);
+//
+template <typename T>
+class InterpolatorLinear: public InterpolatorBase<T, InterpolatorLinear<T> > {
+ public:
+ InterpolatorLinear() { }
+ ~InterpolatorLinear() { }
+
+ protected:
+ // Provides the main implementation of the linear interpolation algorithm.
+ // Assumes that: X[cached_index_] < x < X[cached_index_ + 1]
+ T MethodSpecificInterpolation(T x);
+
+ // Pre-compute internal state_ parameters.
+ bool SetInternalState();
+
+ private:
+ friend class InterpolatorBase<T, InterpolatorLinear<T> >;
+ typedef InterpolatorBase<T, InterpolatorLinear<T> > BaseClass;
+ using BaseClass::status_;
+ using BaseClass::cached_index_;
+ using BaseClass::x_data_;
+ using BaseClass::y_data_;
+ using BaseClass::data_length_;
+ using BaseClass::state_;
+
+ LE_FX_DISALLOW_COPY_AND_ASSIGN(InterpolatorLinear<T>);
+};
+
+template <typename T>
+inline T InterpolatorLinear<T>::MethodSpecificInterpolation(T x) {
+ T dX = x_data_[cached_index_ + 1] - x_data_[cached_index_];
+ T dY = y_data_[cached_index_ + 1] - y_data_[cached_index_];
+ T dx = x - x_data_[cached_index_];
+ return y_data_[cached_index_] + (dY * dx) / dX;
+}
+
+template <typename T>
+bool InterpolatorLinear<T>::SetInternalState() {
+ state_ = NULL;
+ return true;
+}
+
+} // namespace sigmod
+
+} // namespace le_fx
+
+#endif // LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_LINEAR_H_
diff --git a/media/libeffects/proxy/Android.mk b/media/libeffects/proxy/Android.mk
new file mode 100644
index 0000000..01b3be1
--- /dev/null
+++ b/media/libeffects/proxy/Android.mk
@@ -0,0 +1,34 @@
+# Copyright 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_MODULE:= libeffectproxy
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx
+LOCAL_MODULE_TAGS := optional
+
+
+LOCAL_SRC_FILES := \
+ EffectProxy.cpp
+
+LOCAL_CFLAGS+= -fvisibility=hidden
+
+LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libdl libeffects
+
+LOCAL_C_INCLUDES := \
+ system/media/audio_effects/include \
+ bionic/libc/include
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/media/libeffects/proxy/EffectProxy.cpp b/media/libeffects/proxy/EffectProxy.cpp
new file mode 100644
index 0000000..dd4ad08
--- /dev/null
+++ b/media/libeffects/proxy/EffectProxy.cpp
@@ -0,0 +1,339 @@
+/*
+ * 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_TAG "EffectProxy"
+//#define LOG_NDEBUG 0
+
+#include <cutils/log.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <new>
+#include <EffectProxy.h>
+#include <utils/threads.h>
+#include <media/EffectsFactoryApi.h>
+
+namespace android {
+// This is a dummy proxy descriptor just to return to Factory during the initial
+// GetDescriptor call. Later in the factory, it is replaced with the
+// SW sub effect descriptor
+// proxy UUID af8da7e0-2ca1-11e3-b71d-0002a5d5c51b
+const effect_descriptor_t gProxyDescriptor = {
+ EFFECT_UUID_INITIALIZER, // type
+ {0xaf8da7e0, 0x2ca1, 0x11e3, 0xb71d, { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b }}, // uuid
+ EFFECT_CONTROL_API_VERSION, //version of effect control API
+ (EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_LAST |
+ EFFECT_FLAG_VOLUME_CTRL), // effect capability flags
+ 0, // CPU load
+ 1, // Data memory
+ "Proxy", //effect name
+ "AOSP", //implementor name
+};
+
+
+static const effect_descriptor_t *const gDescriptors[] =
+{
+ &gProxyDescriptor,
+};
+
+
+int EffectProxyCreate(const effect_uuid_t *uuid,
+ int32_t sessionId,
+ int32_t ioId,
+ effect_handle_t *pHandle) {
+
+ effect_descriptor_t* desc;
+ EffectContext* pContext;
+ if (pHandle == NULL || uuid == NULL) {
+ ALOGE("EffectProxyCreate() called with NULL pointer");
+ return -EINVAL;
+ }
+ ALOGV("EffectProxyCreate start..");
+ pContext = new EffectContext;
+ pContext->sessionId = sessionId;
+ pContext->ioId = ioId;
+ pContext->uuid = *uuid;
+ pContext->common_itfe = &gEffectInterface;
+
+ // The sub effects will be created in effect_command when the first command
+ // for the effect is received
+ pContext->eHandle[SUB_FX_HOST] = pContext->eHandle[SUB_FX_OFFLOAD] = NULL;
+
+ // Get the HW and SW sub effect descriptors from the effects factory
+ desc = new effect_descriptor_t[SUB_FX_COUNT];
+ pContext->desc = new effect_descriptor_t[SUB_FX_COUNT];
+ int retValue = EffectGetSubEffects(uuid, desc,
+ sizeof(effect_descriptor_t) * SUB_FX_COUNT);
+ // EffectGetSubEffects returns the number of sub-effects copied.
+ if (retValue != SUB_FX_COUNT) {
+ ALOGE("EffectCreate() could not get the sub effects");
+ delete desc;
+ delete pContext->desc;
+ return -EINVAL;
+ }
+ // Check which is the HW descriptor and copy the descriptors
+ // to the Context desc array
+ // Also check if there is only one HW and one SW descriptor.
+ // HW descriptor alone has the HW_TUNNEL flag.
+ if ((desc[0].flags & EFFECT_FLAG_HW_ACC_TUNNEL) &&
+ !(desc[1].flags & EFFECT_FLAG_HW_ACC_TUNNEL)) {
+ pContext->desc[SUB_FX_OFFLOAD] = desc[0];
+ pContext->desc[SUB_FX_HOST] = desc[1];
+ }
+ else if ((desc[1].flags & EFFECT_FLAG_HW_ACC_TUNNEL) &&
+ !(desc[0].flags & EFFECT_FLAG_HW_ACC_TUNNEL)) {
+ pContext->desc[SUB_FX_HOST] = desc[0];
+ pContext->desc[SUB_FX_OFFLOAD] = desc[1];
+ }
+ delete desc;
+#if (LOG_NDEBUG == 0)
+ effect_uuid_t uuid_print = pContext->desc[SUB_FX_HOST].uuid;
+ ALOGV("EffectCreate() UUID of HOST: %08X-%04X-%04X-%04X-%02X%02X%02X%02X"
+ "%02X%02X\n",uuid_print.timeLow, uuid_print.timeMid,
+ uuid_print.timeHiAndVersion, uuid_print.clockSeq, uuid_print.node[0],
+ uuid_print.node[1], uuid_print.node[2], uuid_print.node[3],
+ uuid_print.node[4], uuid_print.node[5]);
+ ALOGV("EffectCreate() UUID of OFFLOAD: %08X-%04X-%04X-%04X-%02X%02X%02X%02X"
+ "%02X%02X\n", uuid_print.timeLow, uuid_print.timeMid,
+ uuid_print.timeHiAndVersion, uuid_print.clockSeq, uuid_print.node[0],
+ uuid_print.node[1], uuid_print.node[2], uuid_print.node[3],
+ uuid_print.node[4], uuid_print.node[5]);
+#endif
+
+ pContext->replySize = PROXY_REPLY_SIZE_DEFAULT;
+ pContext->replyData = (char *)malloc(PROXY_REPLY_SIZE_DEFAULT);
+
+ *pHandle = (effect_handle_t)pContext;
+ ALOGV("EffectCreate end");
+ return 0;
+} //end EffectProxyCreate
+
+int EffectProxyRelease(effect_handle_t handle) {
+ EffectContext * pContext = (EffectContext *)handle;
+ if (pContext == NULL) {
+ ALOGV("ERROR : EffectRelease called with NULL pointer");
+ return -EINVAL;
+ }
+ ALOGV("EffectRelease");
+ delete pContext->desc;
+ free(pContext->replyData);
+
+ if (pContext->eHandle[SUB_FX_HOST])
+ EffectRelease(pContext->eHandle[SUB_FX_HOST]);
+ if (pContext->eHandle[SUB_FX_OFFLOAD])
+ EffectRelease(pContext->eHandle[SUB_FX_OFFLOAD]);
+ delete pContext;
+ pContext = NULL;
+ return 0;
+} /*end EffectProxyRelease */
+
+int EffectProxyGetDescriptor(const effect_uuid_t *uuid,
+ effect_descriptor_t *pDescriptor) {
+ const effect_descriptor_t *desc = NULL;
+
+ if (pDescriptor == NULL || uuid == NULL) {
+ ALOGV("EffectGetDescriptor() called with NULL pointer");
+ return -EINVAL;
+ }
+ desc = &gProxyDescriptor;
+ *pDescriptor = *desc;
+ return 0;
+} /* end EffectProxyGetDescriptor */
+
+/* Effect Control Interface Implementation: Process */
+int Effect_process(effect_handle_t self,
+ audio_buffer_t *inBuffer,
+ audio_buffer_t *outBuffer) {
+
+ EffectContext *pContext = (EffectContext *) self;
+ int ret = 0;
+ if (pContext != NULL) {
+ int index = pContext->index;
+ // if the index refers to HW , do not do anything. Just return.
+ if (index == SUB_FX_HOST) {
+ ret = (*pContext->eHandle[index])->process(pContext->eHandle[index],
+ inBuffer, outBuffer);
+ }
+ }
+ return ret;
+} /* end Effect_process */
+
+/* Effect Control Interface Implementation: Command */
+int Effect_command(effect_handle_t self,
+ uint32_t cmdCode,
+ uint32_t cmdSize,
+ void *pCmdData,
+ uint32_t *replySize,
+ void *pReplyData) {
+
+ EffectContext *pContext = (EffectContext *) self;
+ int status = 0;
+ if (pContext == NULL) {
+ ALOGV("Effect_command() Proxy context is NULL");
+ return -EINVAL;
+ }
+ if (pContext->eHandle[SUB_FX_HOST] == NULL) {
+ ALOGV("Effect_command() Calling HOST EffectCreate");
+ status = EffectCreate(&pContext->desc[SUB_FX_HOST].uuid,
+ pContext->sessionId, pContext->ioId,
+ &(pContext->eHandle[SUB_FX_HOST]));
+ if (status != NO_ERROR || (pContext->eHandle[SUB_FX_HOST] == NULL)) {
+ ALOGV("Effect_command() Error creating SW sub effect");
+ return status;
+ }
+ }
+ if (pContext->eHandle[SUB_FX_OFFLOAD] == NULL) {
+ ALOGV("Effect_command() Calling OFFLOAD EffectCreate");
+ status = EffectCreate(&pContext->desc[SUB_FX_OFFLOAD].uuid,
+ pContext->sessionId, pContext->ioId,
+ &(pContext->eHandle[SUB_FX_OFFLOAD]));
+ if (status != NO_ERROR || (pContext->eHandle[SUB_FX_OFFLOAD] == NULL)) {
+ ALOGV("Effect_command() Error creating HW effect");
+ // Do not return error here as SW effect is created
+ // Return error if the CMD_OFFLOAD sends the index as OFFLOAD
+ }
+ pContext->index = SUB_FX_HOST;
+ }
+ // EFFECT_CMD_OFFLOAD used to (1) send whether the thread is offload or not
+ // (2) Send the ioHandle of the effectThread when the effect
+ // is moved from one type of thread to another.
+ // pCmdData points to a memory holding effect_offload_param_t structure
+ if (cmdCode == EFFECT_CMD_OFFLOAD) {
+ ALOGV("Effect_command() cmdCode = EFFECT_CMD_OFFLOAD");
+ if (cmdSize == 0 || pCmdData == NULL) {
+ ALOGV("effectsOffload: Effect_command: CMD_OFFLOAD has no data");
+ *(int*)pReplyData = FAILED_TRANSACTION;
+ return FAILED_TRANSACTION;
+ }
+ effect_offload_param_t* offloadParam = (effect_offload_param_t*)pCmdData;
+ // Assign the effect context index based on isOffload field of the structure
+ pContext->index = offloadParam->isOffload ? SUB_FX_OFFLOAD : SUB_FX_HOST;
+ // if the index is HW and the HW effect is unavailable, return error
+ // and reset the index to SW
+ if (pContext->eHandle[pContext->index] == NULL) {
+ ALOGV("Effect_command()CMD_OFFLOAD sub effect unavailable");
+ *(int*)pReplyData = FAILED_TRANSACTION;
+ return FAILED_TRANSACTION;
+ }
+ pContext->ioId = offloadParam->ioHandle;
+ ALOGV("Effect_command()CMD_OFFLOAD index:%d io %d", pContext->index, pContext->ioId);
+ // Update the DSP wrapper with the new ioHandle.
+ // Pass the OFFLOAD command to the wrapper.
+ // The DSP wrapper needs to handle this CMD
+ if (pContext->eHandle[SUB_FX_OFFLOAD])
+ status = (*pContext->eHandle[SUB_FX_OFFLOAD])->command(
+ pContext->eHandle[SUB_FX_OFFLOAD], cmdCode, cmdSize,
+ pCmdData, replySize, pReplyData);
+ return status;
+ }
+
+ int index = pContext->index;
+ if (index != SUB_FX_HOST && index != SUB_FX_OFFLOAD) {
+ ALOGV("Effect_command: effect index is neither offload nor host");
+ return -EINVAL;
+ }
+
+ // Getter commands are only sent to the active sub effect.
+ int *subStatus[SUB_FX_COUNT];
+ uint32_t *subReplySize[SUB_FX_COUNT];
+ void *subReplyData[SUB_FX_COUNT];
+ uint32_t tmpSize;
+ int tmpStatus;
+
+ // grow temp reply buffer if needed
+ if (replySize != NULL) {
+ tmpSize = pContext->replySize;
+ while (tmpSize < *replySize && tmpSize < PROXY_REPLY_SIZE_MAX) {
+ tmpSize *= 2;
+ }
+ if (tmpSize > pContext->replySize) {
+ ALOGV("Effect_command grow reply buf to %d", tmpSize);
+ pContext->replyData = (char *)realloc(pContext->replyData, tmpSize);
+ pContext->replySize = tmpSize;
+ }
+ if (tmpSize > *replySize) {
+ tmpSize = *replySize;
+ }
+ } else {
+ tmpSize = 0;
+ }
+ // tmpSize is now the actual reply size for the non active sub effect
+
+ // Send command to sub effects. The command is sent to all sub effects so that their internal
+ // state is kept in sync.
+ // Only the reply from the active sub effect is returned to the caller. The reply from the
+ // other sub effect is lost in pContext->replyData
+ for (int i = 0; i < SUB_FX_COUNT; i++) {
+ if (pContext->eHandle[i] == NULL) {
+ continue;
+ }
+ if (i == index) {
+ subStatus[i] = &status;
+ subReplySize[i] = replySize;
+ subReplyData[i] = pReplyData;
+ } else {
+ subStatus[i] = &tmpStatus;
+ subReplySize[i] = replySize == NULL ? NULL : &tmpSize;
+ subReplyData[i] = pReplyData == NULL ? NULL : pContext->replyData;
+ }
+ *subStatus[i] = (*pContext->eHandle[i])->command(
+ pContext->eHandle[i], cmdCode, cmdSize,
+ pCmdData, subReplySize[i], subReplyData[i]);
+ }
+
+ return status;
+} /* end Effect_command */
+
+
+/* Effect Control Interface Implementation: get_descriptor */
+int Effect_getDescriptor(effect_handle_t self,
+ effect_descriptor_t *pDescriptor) {
+
+ EffectContext * pContext = (EffectContext *) self;
+ const effect_descriptor_t *desc;
+
+ ALOGV("Effect_getDescriptor");
+ if (pContext == NULL || pDescriptor == NULL) {
+ ALOGV("Effect_getDescriptor() invalid param");
+ return -EINVAL;
+ }
+ if (pContext->desc == NULL) {
+ ALOGV("Effect_getDescriptor() could not get descriptor");
+ return -EINVAL;
+ }
+ desc = &pContext->desc[SUB_FX_HOST];
+ *pDescriptor = *desc;
+ pDescriptor->uuid = pContext->uuid; // Replace the uuid with the Proxy UUID
+ // Also set/clear the EFFECT_FLAG_OFFLOAD_SUPPORTED flag based on the sub effects availability
+ if (pContext->eHandle[SUB_FX_OFFLOAD] != NULL)
+ pDescriptor->flags |= EFFECT_FLAG_OFFLOAD_SUPPORTED;
+ else
+ pDescriptor->flags &= ~EFFECT_FLAG_OFFLOAD_SUPPORTED;
+ return 0;
+} /* end Effect_getDescriptor */
+
+} // namespace android
+
+__attribute__ ((visibility ("default")))
+audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = {
+ tag : AUDIO_EFFECT_LIBRARY_TAG,
+ version : EFFECT_LIBRARY_API_VERSION,
+ name : "Effect Proxy",
+ implementor : "AOSP",
+ create_effect : android::EffectProxyCreate,
+ release_effect : android::EffectProxyRelease,
+ get_descriptor : android::EffectProxyGetDescriptor,
+};
diff --git a/media/libeffects/proxy/EffectProxy.h b/media/libeffects/proxy/EffectProxy.h
new file mode 100644
index 0000000..acbe17e
--- /dev/null
+++ b/media/libeffects/proxy/EffectProxy.h
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include <hardware/audio.h>
+#include <hardware/audio_effect.h>
+namespace android {
+enum {
+ SUB_FX_HOST, // Index of HOST in the descriptor and handle arrays
+ // of the Proxy context
+ SUB_FX_OFFLOAD, // Index of OFFLOAD in the descriptor and handle arrays
+ // of the Proxy context
+ SUB_FX_COUNT // The number of sub effects for a Proxy(1 HW, 1 SW)
+};
+#if __cplusplus
+extern "C" {
+#endif
+
+int EffectProxyCreate(const effect_uuid_t *uuid,
+ int32_t sessionId,
+ int32_t ioId,
+ effect_handle_t *pHandle);
+int EffectProxyRelease(effect_handle_t handle);
+int EffectProxyGetDescriptor(const effect_uuid_t *uuid,
+ effect_descriptor_t *pDescriptor);
+/* Effect Control Interface Implementation: Process */
+int Effect_process(effect_handle_t self,
+ audio_buffer_t *inBuffer,
+ audio_buffer_t *outBuffer);
+
+/* Effect Control Interface Implementation: Command */
+int Effect_command(effect_handle_t self,
+ uint32_t cmdCode,
+ uint32_t cmdSize,
+ void *pCmdData,
+ uint32_t *replySize,
+ void *pReplyData);
+int Effect_getDescriptor(effect_handle_t self,
+ effect_descriptor_t *pDescriptor);
+
+const struct effect_interface_s gEffectInterface = {
+ Effect_process,
+ Effect_command,
+ Effect_getDescriptor,
+ NULL,
+};
+
+#define PROXY_REPLY_SIZE_MAX (64 * 1024) // must be power of two
+#define PROXY_REPLY_SIZE_DEFAULT 32 // must be power of two
+
+struct EffectContext {
+ const struct effect_interface_s *common_itfe; // Holds the itfe of the Proxy
+ effect_descriptor_t* desc; // Points to the sub effect descriptors
+ effect_handle_t eHandle[SUB_FX_COUNT]; // The effect handles of the sub effects
+ int index; // The index that is currently active - HOST or OFFLOAD
+ int32_t sessionId; // The sessiond in which the effect is created.
+ // Stored in context to pass on to sub effect creation
+ int32_t ioId; // The ioId in which the effect is created.
+ // Stored in context to pass on to sub effect creation
+ effect_uuid_t uuid; // UUID of the Proxy
+ char* replyData; // temporary buffer for non active sub effect command reply
+ uint32_t replySize; // current size of temporary reply buffer
+};
+
+#if __cplusplus
+} // extern "C"
+#endif
+} //namespace android
diff --git a/media/libeffects/testlibs/AudioFormatAdapter.h b/media/libeffects/testlibs/AudioFormatAdapter.h
index 41f1810..dea2734 100644
--- a/media/libeffects/testlibs/AudioFormatAdapter.h
+++ b/media/libeffects/testlibs/AudioFormatAdapter.h
@@ -75,6 +75,7 @@ public:
while (numSamples > 0) {
uint32_t numSamplesIter = min(numSamples, mMaxSamplesPerCall);
uint32_t nSamplesChannels = numSamplesIter * mNumChannels;
+ // This branch of "if" is untested
if (mPcmFormat == AUDIO_FORMAT_PCM_8_24_BIT) {
if (mBehavior == EFFECT_BUFFER_ACCESS_WRITE) {
mpProcessor->process(
diff --git a/media/libeffects/testlibs/EffectEqualizer.cpp b/media/libeffects/testlibs/EffectEqualizer.cpp
index c35453b..8d00206 100644
--- a/media/libeffects/testlibs/EffectEqualizer.cpp
+++ b/media/libeffects/testlibs/EffectEqualizer.cpp
@@ -234,8 +234,7 @@ int Equalizer_setConfig(EqualizerContext *pContext, effect_config_t *pConfig)
(pConfig->inputCfg.channels == AUDIO_CHANNEL_OUT_STEREO));
CHECK_ARG(pConfig->outputCfg.accessMode == EFFECT_BUFFER_ACCESS_WRITE
|| pConfig->outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE);
- CHECK_ARG(pConfig->inputCfg.format == AUDIO_FORMAT_PCM_8_24_BIT
- || pConfig->inputCfg.format == AUDIO_FORMAT_PCM_16_BIT);
+ CHECK_ARG(pConfig->inputCfg.format == AUDIO_FORMAT_PCM_16_BIT);
int channelCount;
if (pConfig->inputCfg.channels == AUDIO_CHANNEL_OUT_MONO) {
diff --git a/media/libeffects/visualizer/EffectVisualizer.cpp b/media/libeffects/visualizer/EffectVisualizer.cpp
index 9f8139c..2d66eef 100644
--- a/media/libeffects/visualizer/EffectVisualizer.cpp
+++ b/media/libeffects/visualizer/EffectVisualizer.cpp
@@ -22,6 +22,7 @@
#include <string.h>
#include <new>
#include <time.h>
+#include <math.h>
#include <audio_effects/effect_visualizer.h>
@@ -54,6 +55,18 @@ enum visualizer_state_e {
#define CAPTURE_BUF_SIZE 65536 // "64k should be enough for everyone"
+#define DISCARD_MEASUREMENTS_TIME_MS 2000 // discard measurements older than this number of ms
+
+// maximum number of buffers for which we keep track of the measurements
+#define MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS 25 // note: buffer index is stored in uint8_t
+
+
+struct BufferStats {
+ bool mIsValid;
+ uint16_t mPeakU16; // the positive peak of the absolute value of the samples in a buffer
+ float mRmsSquared; // the average square of the samples in a buffer
+};
+
struct VisualizerContext {
const struct effect_interface_s *mItfe;
effect_config_t mConfig;
@@ -61,15 +74,38 @@ struct VisualizerContext {
uint32_t mCaptureSize;
uint32_t mScalingMode;
uint8_t mState;
- uint8_t mLastCaptureIdx;
+ uint32_t mLastCaptureIdx;
uint32_t mLatency;
struct timespec mBufferUpdateTime;
uint8_t mCaptureBuf[CAPTURE_BUF_SIZE];
+ // for measurements
+ uint8_t mChannelCount; // to avoid recomputing it every time a buffer is processed
+ uint32_t mMeasurementMode;
+ uint8_t mMeasurementWindowSizeInBuffers;
+ uint8_t mMeasurementBufferIdx;
+ BufferStats mPastMeasurements[MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS];
};
//
//--- Local functions
//
+uint32_t Visualizer_getDeltaTimeMsFromUpdatedTime(VisualizerContext* pContext) {
+ uint32_t deltaMs = 0;
+ if (pContext->mBufferUpdateTime.tv_sec != 0) {
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+ time_t secs = ts.tv_sec - pContext->mBufferUpdateTime.tv_sec;
+ long nsec = ts.tv_nsec - pContext->mBufferUpdateTime.tv_nsec;
+ if (nsec < 0) {
+ --secs;
+ nsec += 1000000000;
+ }
+ deltaMs = secs * 1000 + nsec / 1000000;
+ }
+ }
+ return deltaMs;
+}
+
void Visualizer_reset(VisualizerContext *pContext)
{
@@ -165,9 +201,21 @@ int Visualizer_init(VisualizerContext *pContext)
pContext->mConfig.outputCfg.bufferProvider.cookie = NULL;
pContext->mConfig.outputCfg.mask = EFFECT_CONFIG_ALL;
+ // visualization initialization
pContext->mCaptureSize = VISUALIZER_CAPTURE_SIZE_MAX;
pContext->mScalingMode = VISUALIZER_SCALING_MODE_NORMALIZED;
+ // measurement initialization
+ pContext->mChannelCount = popcount(pContext->mConfig.inputCfg.channels);
+ pContext->mMeasurementMode = MEASUREMENT_MODE_NONE;
+ pContext->mMeasurementWindowSizeInBuffers = MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS;
+ pContext->mMeasurementBufferIdx = 0;
+ for (uint32_t i=0 ; i<pContext->mMeasurementWindowSizeInBuffers ; i++) {
+ pContext->mPastMeasurements[i].mIsValid = false;
+ pContext->mPastMeasurements[i].mPeakU16 = 0;
+ pContext->mPastMeasurements[i].mRmsSquared = 0;
+ }
+
Visualizer_setConfig(pContext, &pContext->mConfig);
return 0;
@@ -270,6 +318,30 @@ int Visualizer_process(
return -EINVAL;
}
+ // perform measurements if needed
+ if (pContext->mMeasurementMode & MEASUREMENT_MODE_PEAK_RMS) {
+ // find the peak and RMS squared for the new buffer
+ uint32_t inIdx;
+ int16_t maxSample = 0;
+ float rmsSqAcc = 0;
+ for (inIdx = 0 ; inIdx < inBuffer->frameCount * pContext->mChannelCount ; inIdx++) {
+ if (inBuffer->s16[inIdx] > maxSample) {
+ maxSample = inBuffer->s16[inIdx];
+ } else if (-inBuffer->s16[inIdx] > maxSample) {
+ maxSample = -inBuffer->s16[inIdx];
+ }
+ rmsSqAcc += (inBuffer->s16[inIdx] * inBuffer->s16[inIdx]);
+ }
+ // store the measurement
+ pContext->mPastMeasurements[pContext->mMeasurementBufferIdx].mPeakU16 = (uint16_t)maxSample;
+ pContext->mPastMeasurements[pContext->mMeasurementBufferIdx].mRmsSquared =
+ rmsSqAcc / (inBuffer->frameCount * pContext->mChannelCount);
+ pContext->mPastMeasurements[pContext->mMeasurementBufferIdx].mIsValid = true;
+ if (++pContext->mMeasurementBufferIdx >= pContext->mMeasurementWindowSizeInBuffers) {
+ pContext->mMeasurementBufferIdx = 0;
+ }
+ }
+
// all code below assumes stereo 16 bit PCM output and input
int32_t shift;
@@ -423,6 +495,12 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
p->vsize = sizeof(uint32_t);
*replySize += sizeof(uint32_t);
break;
+ case VISUALIZER_PARAM_MEASUREMENT_MODE:
+ ALOGV("get mMeasurementMode = %d", pContext->mMeasurementMode);
+ *((uint32_t *)p->data + 1) = pContext->mMeasurementMode;
+ p->vsize = sizeof(uint32_t);
+ *replySize += sizeof(uint32_t);
+ break;
default:
p->status = -EINVAL;
}
@@ -452,6 +530,10 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
pContext->mLatency = *((uint32_t *)p->data + 1);
ALOGV("set mLatency = %d", pContext->mLatency);
break;
+ case VISUALIZER_PARAM_MEASUREMENT_MODE:
+ pContext->mMeasurementMode = *((uint32_t *)p->data + 1);
+ ALOGV("set mMeasurementMode = %d", pContext->mMeasurementMode);
+ break;
default:
*(int32_t *)pReplyData = -EINVAL;
}
@@ -470,24 +552,12 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
}
if (pContext->mState == VISUALIZER_STATE_ACTIVE) {
int32_t latencyMs = pContext->mLatency;
- uint32_t deltaMs = 0;
- if (pContext->mBufferUpdateTime.tv_sec != 0) {
- struct timespec ts;
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
- time_t secs = ts.tv_sec - pContext->mBufferUpdateTime.tv_sec;
- long nsec = ts.tv_nsec - pContext->mBufferUpdateTime.tv_nsec;
- if (nsec < 0) {
- --secs;
- nsec += 1000000000;
- }
- deltaMs = secs * 1000 + nsec / 1000000;
- latencyMs -= deltaMs;
- if (latencyMs < 0) {
- latencyMs = 0;
- }
- }
+ const uint32_t deltaMs = Visualizer_getDeltaTimeMsFromUpdatedTime(pContext);
+ latencyMs -= deltaMs;
+ if (latencyMs < 0) {
+ latencyMs = 0;
}
- uint32_t deltaSmpl = pContext->mConfig.inputCfg.samplingRate * latencyMs / 1000;
+ const uint32_t deltaSmpl = pContext->mConfig.inputCfg.samplingRate * latencyMs / 1000;
int32_t capturePoint = pContext->mCaptureIdx - pContext->mCaptureSize - deltaSmpl;
int32_t captureSize = pContext->mCaptureSize;
@@ -499,7 +569,7 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
memcpy(pReplyData,
pContext->mCaptureBuf + CAPTURE_BUF_SIZE + capturePoint,
size);
- pReplyData += size;
+ pReplyData = (char *)pReplyData + size;
captureSize -= size;
capturePoint = 0;
}
@@ -525,6 +595,54 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
break;
+ case VISUALIZER_CMD_MEASURE: {
+ uint16_t peakU16 = 0;
+ float sumRmsSquared = 0.0f;
+ uint8_t nbValidMeasurements = 0;
+ // reset measurements if last measurement was too long ago (which implies stored
+ // measurements aren't relevant anymore and shouldn't bias the new one)
+ const int32_t delayMs = Visualizer_getDeltaTimeMsFromUpdatedTime(pContext);
+ if (delayMs > DISCARD_MEASUREMENTS_TIME_MS) {
+ ALOGV("Discarding measurements, last measurement is %dms old", delayMs);
+ for (uint32_t i=0 ; i<pContext->mMeasurementWindowSizeInBuffers ; i++) {
+ pContext->mPastMeasurements[i].mIsValid = false;
+ pContext->mPastMeasurements[i].mPeakU16 = 0;
+ pContext->mPastMeasurements[i].mRmsSquared = 0;
+ }
+ pContext->mMeasurementBufferIdx = 0;
+ } else {
+ // only use actual measurements, otherwise the first RMS measure happening before
+ // MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS have been played will always be artificially
+ // low
+ for (uint32_t i=0 ; i < pContext->mMeasurementWindowSizeInBuffers ; i++) {
+ if (pContext->mPastMeasurements[i].mIsValid) {
+ if (pContext->mPastMeasurements[i].mPeakU16 > peakU16) {
+ peakU16 = pContext->mPastMeasurements[i].mPeakU16;
+ }
+ sumRmsSquared += pContext->mPastMeasurements[i].mRmsSquared;
+ nbValidMeasurements++;
+ }
+ }
+ }
+ float rms = nbValidMeasurements == 0 ? 0.0f : sqrtf(sumRmsSquared / nbValidMeasurements);
+ int32_t* pIntReplyData = (int32_t*)pReplyData;
+ // convert from I16 sample values to mB and write results
+ if (rms < 0.000016f) {
+ pIntReplyData[MEASUREMENT_IDX_RMS] = -9600; //-96dB
+ } else {
+ pIntReplyData[MEASUREMENT_IDX_RMS] = (int32_t) (2000 * log10(rms / 32767.0f));
+ }
+ if (peakU16 == 0) {
+ pIntReplyData[MEASUREMENT_IDX_PEAK] = -9600; //-96dB
+ } else {
+ pIntReplyData[MEASUREMENT_IDX_PEAK] = (int32_t) (2000 * log10(peakU16 / 32767.0f));
+ }
+ ALOGV("VISUALIZER_CMD_MEASURE peak=%d (%dmB), rms=%.1f (%dmB)",
+ peakU16, pIntReplyData[MEASUREMENT_IDX_PEAK],
+ rms, pIntReplyData[MEASUREMENT_IDX_RMS]);
+ }
+ break;
+
default:
ALOGW("Visualizer_command invalid command %d",cmdCode);
return -EINVAL;
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index 2c0c3a5..56e7787 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -53,7 +53,8 @@ LOCAL_SRC_FILES:= \
Visualizer.cpp \
MemoryLeakTrackUtil.cpp \
SoundPool.cpp \
- SoundPoolThread.cpp
+ SoundPoolThread.cpp \
+ StringArray.cpp
LOCAL_SRC_FILES += ../libnbaio/roundup.c
@@ -61,6 +62,7 @@ LOCAL_SRC_FILES += ../libnbaio/roundup.c
LOCAL_CFLAGS += -DANDROID_SMP=$(if $(findstring true,$(TARGET_CPU_SMP)),1,0)
LOCAL_SRC_FILES += SingleStateQueue.cpp
LOCAL_CFLAGS += -DSINGLE_STATE_QUEUE_INSTANTIATIONS='"SingleStateQueueInstantiations.cpp"'
+# Consider a separate a library for SingleStateQueueInstantiations.
LOCAL_SHARED_LIBRARIES := \
libui liblog libcutils libutils libbinder libsonivox libicuuc libexpat \
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 40ff1bf..666fafa 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -19,17 +19,13 @@
#define LOG_TAG "AudioRecord"
#include <sys/resource.h>
-#include <sys/types.h>
-
#include <binder/IPCThreadState.h>
-#include <cutils/atomic.h>
-#include <cutils/compiler.h>
#include <media/AudioRecord.h>
-#include <media/AudioSystem.h>
-#include <system/audio.h>
#include <utils/Log.h>
-
#include <private/media/AudioTrackShared.h>
+#include <media/IAudioFlinger.h>
+
+#define WAIT_PERIOD_MS 10
namespace android {
// ---------------------------------------------------------------------------
@@ -41,7 +37,9 @@ status_t AudioRecord::getMinFrameCount(
audio_format_t format,
audio_channel_mask_t channelMask)
{
- if (frameCount == NULL) return BAD_VALUE;
+ if (frameCount == NULL) {
+ return BAD_VALUE;
+ }
// default to 0 in case of error
*frameCount = 0;
@@ -62,10 +60,9 @@ status_t AudioRecord::getMinFrameCount(
// We double the size of input buffer for ping pong use of record buffer.
size <<= 1;
- if (audio_is_linear_pcm(format)) {
- uint32_t channelCount = popcount(channelMask);
- size /= channelCount * audio_bytes_per_sample(format);
- }
+ // Assumes audio_is_linear_pcm(format)
+ uint32_t channelCount = popcount(channelMask);
+ size /= channelCount * audio_bytes_per_sample(format);
*frameCount = size;
return NO_ERROR;
@@ -75,8 +72,7 @@ status_t AudioRecord::getMinFrameCount(
AudioRecord::AudioRecord()
: mStatus(NO_INIT), mSessionId(0),
- mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT),
- mProxy(NULL)
+ mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT)
{
}
@@ -89,14 +85,16 @@ AudioRecord::AudioRecord(
callback_t cbf,
void* user,
int notificationFrames,
- int sessionId)
+ int sessionId,
+ transfer_type transferType,
+ audio_input_flags_t flags)
: mStatus(NO_INIT), mSessionId(0),
mPreviousPriority(ANDROID_PRIORITY_NORMAL),
mPreviousSchedulingGroup(SP_DEFAULT),
mProxy(NULL)
{
- mStatus = set(inputSource, sampleRate, format, channelMask,
- frameCount, cbf, user, notificationFrames, false /*threadCanCallJava*/, sessionId);
+ mStatus = set(inputSource, sampleRate, format, channelMask, frameCount, cbf, user,
+ notificationFrames, false /*threadCanCallJava*/, sessionId, transferType);
}
AudioRecord::~AudioRecord()
@@ -107,15 +105,18 @@ AudioRecord::~AudioRecord()
// Otherwise the callback thread will never exit.
stop();
if (mAudioRecordThread != 0) {
+ mProxy->interrupt();
mAudioRecordThread->requestExit(); // see comment in AudioRecord.h
mAudioRecordThread->requestExitAndWait();
mAudioRecordThread.clear();
}
- mAudioRecord.clear();
+ if (mAudioRecord != 0) {
+ mAudioRecord->asBinder()->unlinkToDeath(mDeathNotifier, this);
+ mAudioRecord.clear();
+ }
IPCThreadState::self()->flushCommands();
AudioSystem::releaseAudioSessionId(mSessionId);
}
- delete mProxy;
}
status_t AudioRecord::set(
@@ -128,8 +129,33 @@ status_t AudioRecord::set(
void* user,
int notificationFrames,
bool threadCanCallJava,
- int sessionId)
+ int sessionId,
+ transfer_type transferType,
+ audio_input_flags_t flags)
{
+ switch (transferType) {
+ case TRANSFER_DEFAULT:
+ if (cbf == NULL || threadCanCallJava) {
+ transferType = TRANSFER_SYNC;
+ } else {
+ transferType = TRANSFER_CALLBACK;
+ }
+ break;
+ case TRANSFER_CALLBACK:
+ if (cbf == NULL) {
+ ALOGE("Transfer type TRANSFER_CALLBACK but cbf == NULL");
+ return BAD_VALUE;
+ }
+ break;
+ case TRANSFER_OBTAIN:
+ case TRANSFER_SYNC:
+ break;
+ default:
+ ALOGE("Invalid transfer type %d", transferType);
+ return BAD_VALUE;
+ }
+ mTransfer = transferType;
+
// FIXME "int" here is legacy and will be replaced by size_t later
if (frameCountInt < 0) {
ALOGE("Invalid frame count %d", frameCountInt);
@@ -143,15 +169,18 @@ status_t AudioRecord::set(
AutoMutex lock(mLock);
if (mAudioRecord != 0) {
+ ALOGE("Track already in use");
return INVALID_OPERATION;
}
if (inputSource == AUDIO_SOURCE_DEFAULT) {
inputSource = AUDIO_SOURCE_MIC;
}
+ mInputSource = inputSource;
if (sampleRate == 0) {
- sampleRate = DEFAULT_SAMPLE_RATE;
+ ALOGE("Invalid sample rate %u", sampleRate);
+ return BAD_VALUE;
}
mSampleRate = sampleRate;
@@ -159,47 +188,36 @@ status_t AudioRecord::set(
if (format == AUDIO_FORMAT_DEFAULT) {
format = AUDIO_FORMAT_PCM_16_BIT;
}
+
// validate parameters
if (!audio_is_valid_format(format)) {
- ALOGE("Invalid format");
+ ALOGE("Invalid format %d", format);
+ return BAD_VALUE;
+ }
+ // Temporary restriction: AudioFlinger currently supports 16-bit PCM only
+ if (format != AUDIO_FORMAT_PCM_16_BIT) {
+ ALOGE("Format %d is not supported", format);
return BAD_VALUE;
}
mFormat = format;
if (!audio_is_input_channel(channelMask)) {
+ ALOGE("Invalid channel mask %#x", channelMask);
return BAD_VALUE;
}
mChannelMask = channelMask;
uint32_t channelCount = popcount(channelMask);
mChannelCount = channelCount;
- if (audio_is_linear_pcm(format)) {
- mFrameSize = channelCount * audio_bytes_per_sample(format);
- } else {
- mFrameSize = sizeof(uint8_t);
- }
-
- if (sessionId == 0 ) {
- mSessionId = AudioSystem::newAudioSessionId();
- } else {
- mSessionId = sessionId;
- }
- ALOGV("set(): mSessionId %d", mSessionId);
-
- audio_io_handle_t input = AudioSystem::getInput(inputSource,
- sampleRate,
- format,
- channelMask,
- mSessionId);
- if (input == 0) {
- ALOGE("Could not get audio input for record source %d", inputSource);
- return BAD_VALUE;
- }
+ // Assumes audio_is_linear_pcm(format), else sizeof(uint8_t)
+ mFrameSize = channelCount * audio_bytes_per_sample(format);
// validate framecount
size_t minFrameCount = 0;
- status_t status = getMinFrameCount(&minFrameCount, sampleRate, format, channelMask);
+ status_t status = AudioRecord::getMinFrameCount(&minFrameCount,
+ sampleRate, format, channelMask);
if (status != NO_ERROR) {
+ ALOGE("getMinFrameCount() failed; status %d", status);
return status;
}
ALOGV("AudioRecord::set() minFrameCount = %d", minFrameCount);
@@ -207,16 +225,26 @@ status_t AudioRecord::set(
if (frameCount == 0) {
frameCount = minFrameCount;
} else if (frameCount < minFrameCount) {
+ ALOGE("frameCount %u < minFrameCount %u", frameCount, minFrameCount);
return BAD_VALUE;
}
+ mFrameCount = frameCount;
- if (notificationFrames == 0) {
- notificationFrames = frameCount/2;
+ mNotificationFramesReq = notificationFrames;
+ mNotificationFramesAct = 0;
+
+ if (sessionId == 0 ) {
+ mSessionId = AudioSystem::newAudioSessionId();
+ } else {
+ mSessionId = sessionId;
}
+ ALOGV("set(): mSessionId %d", mSessionId);
+
+ mFlags = flags;
// create the IAudioRecord
- status = openRecord_l(sampleRate, format, frameCount, input);
- if (status != NO_ERROR) {
+ status = openRecord_l(0 /*epoch*/);
+ if (status) {
return status;
}
@@ -232,8 +260,7 @@ status_t AudioRecord::set(
mActive = false;
mCbf = cbf;
- mNotificationFrames = notificationFrames;
- mRemainingFrames = notificationFrames;
+ mRefreshRemaining = true;
mUserData = user;
// TODO: add audio hardware input latency here
mLatency = (1000*mFrameCount) / sampleRate;
@@ -241,120 +268,79 @@ status_t AudioRecord::set(
mMarkerReached = false;
mNewPosition = 0;
mUpdatePeriod = 0;
- mInputSource = inputSource;
- mInput = input;
AudioSystem::acquireAudioSessionId(mSessionId);
+ mSequence = 1;
+ mObservedSequence = mSequence;
+ mInOverrun = false;
return NO_ERROR;
}
-status_t AudioRecord::initCheck() const
-{
- return mStatus;
-}
-
-// -------------------------------------------------------------------------
-
-uint32_t AudioRecord::latency() const
-{
- return mLatency;
-}
-
-audio_format_t AudioRecord::format() const
-{
- return mFormat;
-}
-
-uint32_t AudioRecord::channelCount() const
-{
- return mChannelCount;
-}
-
-size_t AudioRecord::frameCount() const
-{
- return mFrameCount;
-}
-
-audio_source_t AudioRecord::inputSource() const
-{
- return mInputSource;
-}
-
// -------------------------------------------------------------------------
status_t AudioRecord::start(AudioSystem::sync_event_t event, int triggerSession)
{
- status_t ret = NO_ERROR;
- sp<AudioRecordThread> t = mAudioRecordThread;
-
ALOGV("start, sync event %d trigger session %d", event, triggerSession);
AutoMutex lock(mLock);
- // acquire a strong reference on the IAudioRecord and IMemory so that they cannot be destroyed
- // while we are accessing the cblk
- sp<IAudioRecord> audioRecord = mAudioRecord;
- sp<IMemory> iMem = mCblkMemory;
- audio_track_cblk_t* cblk = mCblk;
+ if (mActive) {
+ return NO_ERROR;
+ }
- if (!mActive) {
- mActive = true;
+ // reset current position as seen by client to 0
+ mProxy->setEpoch(mProxy->getEpoch() - mProxy->getPosition());
- cblk->lock.lock();
- if (!(cblk->flags & CBLK_INVALID)) {
- cblk->lock.unlock();
- ALOGV("mAudioRecord->start()");
- ret = mAudioRecord->start(event, triggerSession);
- cblk->lock.lock();
- if (ret == DEAD_OBJECT) {
- android_atomic_or(CBLK_INVALID, &cblk->flags);
- }
- }
- if (cblk->flags & CBLK_INVALID) {
- audio_track_cblk_t* temp = cblk;
- ret = restoreRecord_l(temp);
- cblk = temp;
+ mNewPosition = mProxy->getPosition() + mUpdatePeriod;
+ int32_t flags = android_atomic_acquire_load(&mCblk->mFlags);
+
+ status_t status = NO_ERROR;
+ if (!(flags & CBLK_INVALID)) {
+ ALOGV("mAudioRecord->start()");
+ status = mAudioRecord->start(event, triggerSession);
+ if (status == DEAD_OBJECT) {
+ flags |= CBLK_INVALID;
}
- cblk->lock.unlock();
- if (ret == NO_ERROR) {
- mNewPosition = cblk->user + mUpdatePeriod;
- cblk->bufferTimeoutMs = (event == AudioSystem::SYNC_EVENT_NONE) ? MAX_RUN_TIMEOUT_MS :
- AudioSystem::kSyncRecordStartTimeOutMs;
- cblk->waitTimeMs = 0;
- if (t != 0) {
- t->resume();
- } else {
- mPreviousPriority = getpriority(PRIO_PROCESS, 0);
- get_sched_policy(0, &mPreviousSchedulingGroup);
- androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
- }
+ }
+ if (flags & CBLK_INVALID) {
+ status = restoreRecord_l("start");
+ }
+
+ if (status != NO_ERROR) {
+ ALOGE("start() status %d", status);
+ } else {
+ mActive = true;
+ sp<AudioRecordThread> t = mAudioRecordThread;
+ if (t != 0) {
+ t->resume();
} else {
- mActive = false;
+ mPreviousPriority = getpriority(PRIO_PROCESS, 0);
+ get_sched_policy(0, &mPreviousSchedulingGroup);
+ androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
}
}
- return ret;
+ return status;
}
void AudioRecord::stop()
{
- sp<AudioRecordThread> t = mAudioRecordThread;
-
- ALOGV("stop");
-
AutoMutex lock(mLock);
- if (mActive) {
- mActive = false;
- mCblk->cv.signal();
- mAudioRecord->stop();
- // the record head position will reset to 0, so if a marker is set, we need
- // to activate it again
- mMarkerReached = false;
- if (t != 0) {
- t->pause();
- } else {
- setpriority(PRIO_PROCESS, 0, mPreviousPriority);
- set_sched_policy(0, mPreviousSchedulingGroup);
- }
+ if (!mActive) {
+ return;
+ }
+
+ mActive = false;
+ mProxy->interrupt();
+ mAudioRecord->stop();
+ // the record head position will reset to 0, so if a marker is set, we need
+ // to activate it again
+ mMarkerReached = false;
+ sp<AudioRecordThread> t = mAudioRecordThread;
+ if (t != 0) {
+ t->pause();
+ } else {
+ setpriority(PRIO_PROCESS, 0, mPreviousPriority);
+ set_sched_policy(0, mPreviousSchedulingGroup);
}
}
@@ -364,14 +350,11 @@ bool AudioRecord::stopped() const
return !mActive;
}
-uint32_t AudioRecord::getSampleRate() const
-{
- return mSampleRate;
-}
-
status_t AudioRecord::setMarkerPosition(uint32_t marker)
{
- if (mCbf == NULL) return INVALID_OPERATION;
+ if (mCbf == NULL) {
+ return INVALID_OPERATION;
+ }
AutoMutex lock(mLock);
mMarkerPosition = marker;
@@ -382,7 +365,9 @@ status_t AudioRecord::setMarkerPosition(uint32_t marker)
status_t AudioRecord::getMarkerPosition(uint32_t *marker) const
{
- if (marker == NULL) return BAD_VALUE;
+ if (marker == NULL) {
+ return BAD_VALUE;
+ }
AutoMutex lock(mLock);
*marker = mMarkerPosition;
@@ -392,13 +377,12 @@ status_t AudioRecord::getMarkerPosition(uint32_t *marker) const
status_t AudioRecord::setPositionUpdatePeriod(uint32_t updatePeriod)
{
- if (mCbf == NULL) return INVALID_OPERATION;
-
- uint32_t curPosition;
- getPosition(&curPosition);
+ if (mCbf == NULL) {
+ return INVALID_OPERATION;
+ }
AutoMutex lock(mLock);
- mNewPosition = curPosition + updatePeriod;
+ mNewPosition = mProxy->getPosition() + updatePeriod;
mUpdatePeriod = updatePeriod;
return NO_ERROR;
@@ -406,7 +390,9 @@ status_t AudioRecord::setPositionUpdatePeriod(uint32_t updatePeriod)
status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) const
{
- if (updatePeriod == NULL) return BAD_VALUE;
+ if (updatePeriod == NULL) {
+ return BAD_VALUE;
+ }
AutoMutex lock(mLock);
*updatePeriod = mUpdatePeriod;
@@ -416,10 +402,12 @@ status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) const
status_t AudioRecord::getPosition(uint32_t *position) const
{
- if (position == NULL) return BAD_VALUE;
+ if (position == NULL) {
+ return BAD_VALUE;
+ }
AutoMutex lock(mLock);
- *position = mCblk->user;
+ *position = mProxy->getPosition();
return NO_ERROR;
}
@@ -427,17 +415,13 @@ status_t AudioRecord::getPosition(uint32_t *position) const
unsigned int AudioRecord::getInputFramesLost() const
{
// no need to check mActive, because if inactive this will return 0, which is what we want
- return AudioSystem::getInputFramesLost(mInput);
+ return AudioSystem::getInputFramesLost(getInput());
}
// -------------------------------------------------------------------------
// must be called with mLock held
-status_t AudioRecord::openRecord_l(
- uint32_t sampleRate,
- audio_format_t format,
- size_t frameCount,
- audio_io_handle_t input)
+status_t AudioRecord::openRecord_l(size_t epoch)
{
status_t status;
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
@@ -446,23 +430,53 @@ status_t AudioRecord::openRecord_l(
return NO_INIT;
}
+ IAudioFlinger::track_flags_t trackFlags = IAudioFlinger::TRACK_DEFAULT;
pid_t tid = -1;
- // FIXME see similar logic at AudioTrack
+
+ // Client can only express a preference for FAST. Server will perform additional tests.
+ // The only supported use case for FAST is callback transfer mode.
+ if (mFlags & AUDIO_INPUT_FLAG_FAST) {
+ if ((mTransfer != TRANSFER_CALLBACK) || (mAudioRecordThread == 0)) {
+ ALOGW("AUDIO_INPUT_FLAG_FAST denied by client");
+ // once denied, do not request again if IAudioRecord is re-created
+ mFlags = (audio_input_flags_t) (mFlags & ~AUDIO_INPUT_FLAG_FAST);
+ } else {
+ trackFlags |= IAudioFlinger::TRACK_FAST;
+ tid = mAudioRecordThread->getTid();
+ }
+ }
+
+ mNotificationFramesAct = mNotificationFramesReq;
+
+ if (!(mFlags & AUDIO_INPUT_FLAG_FAST)) {
+ // Make sure that application is notified with sufficient margin before overrun
+ if (mNotificationFramesAct == 0 || mNotificationFramesAct > mFrameCount/2) {
+ mNotificationFramesAct = mFrameCount/2;
+ }
+ }
+
+ audio_io_handle_t input = AudioSystem::getInput(mInputSource, mSampleRate, mFormat,
+ mChannelMask, mSessionId);
+ if (input == 0) {
+ ALOGE("Could not get audio input for record source %d", mInputSource);
+ return BAD_VALUE;
+ }
int originalSessionId = mSessionId;
sp<IAudioRecord> record = audioFlinger->openRecord(input,
- sampleRate, format,
+ mSampleRate, mFormat,
mChannelMask,
- frameCount,
- IAudioFlinger::TRACK_DEFAULT,
+ mFrameCount,
+ &trackFlags,
tid,
&mSessionId,
&status);
ALOGE_IF(originalSessionId != 0 && mSessionId != originalSessionId,
"session ID changed from %d to %d", originalSessionId, mSessionId);
- if (record == 0) {
+ if (record == 0 || status != NO_ERROR) {
ALOGE("AudioFlinger could not create record track, status: %d", status);
+ AudioSystem::releaseInput(input);
return status;
}
sp<IMemory> iMem = record->getCblk();
@@ -470,133 +484,163 @@ status_t AudioRecord::openRecord_l(
ALOGE("Could not get control block");
return NO_INIT;
}
- mAudioRecord.clear();
+ void *iMemPointer = iMem->pointer();
+ if (iMemPointer == NULL) {
+ ALOGE("Could not get control block pointer");
+ return NO_INIT;
+ }
+ if (mAudioRecord != 0) {
+ mAudioRecord->asBinder()->unlinkToDeath(mDeathNotifier, this);
+ mDeathNotifier.clear();
+ }
+ mInput = input;
mAudioRecord = record;
- mCblkMemory.clear();
mCblkMemory = iMem;
- audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMem->pointer());
+ audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer);
mCblk = cblk;
- mBuffers = (char*)cblk + sizeof(audio_track_cblk_t);
- cblk->bufferTimeoutMs = MAX_RUN_TIMEOUT_MS;
- cblk->waitTimeMs = 0;
+ // FIXME missing fast track frameCount logic
+ mAwaitBoost = false;
+ if (mFlags & AUDIO_INPUT_FLAG_FAST) {
+ if (trackFlags & IAudioFlinger::TRACK_FAST) {
+ ALOGV("AUDIO_INPUT_FLAG_FAST successful; frameCount %u", mFrameCount);
+ mAwaitBoost = true;
+ // double-buffering is not required for fast tracks, due to tighter scheduling
+ if (mNotificationFramesAct == 0 || mNotificationFramesAct > mFrameCount) {
+ mNotificationFramesAct = mFrameCount;
+ }
+ } else {
+ ALOGV("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %u", mFrameCount);
+ // once denied, do not request again if IAudioRecord is re-created
+ mFlags = (audio_input_flags_t) (mFlags & ~AUDIO_INPUT_FLAG_FAST);
+ if (mNotificationFramesAct == 0 || mNotificationFramesAct > mFrameCount/2) {
+ mNotificationFramesAct = mFrameCount/2;
+ }
+ }
+ }
+
+ // starting address of buffers in shared memory
+ void *buffers = (char*)cblk + sizeof(audio_track_cblk_t);
// update proxy
- delete mProxy;
- mProxy = new AudioRecordClientProxy(cblk, mBuffers, frameCount, mFrameSize);
+ mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mFrameSize);
+ mProxy->setEpoch(epoch);
+ mProxy->setMinimum(mNotificationFramesAct);
+
+ mDeathNotifier = new DeathNotifier(this);
+ mAudioRecord->asBinder()->linkToDeath(mDeathNotifier, this);
return NO_ERROR;
}
status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, int32_t waitCount)
{
- ALOG_ASSERT(mStatus == NO_ERROR && mProxy != NULL);
+ if (audioBuffer == NULL) {
+ return BAD_VALUE;
+ }
+ if (mTransfer != TRANSFER_OBTAIN) {
+ audioBuffer->frameCount = 0;
+ audioBuffer->size = 0;
+ audioBuffer->raw = NULL;
+ return INVALID_OPERATION;
+ }
- AutoMutex lock(mLock);
- bool active;
- status_t result = NO_ERROR;
- audio_track_cblk_t* cblk = mCblk;
- uint32_t framesReq = audioBuffer->frameCount;
- uint32_t waitTimeMs = (waitCount < 0) ? cblk->bufferTimeoutMs : WAIT_PERIOD_MS;
-
- audioBuffer->frameCount = 0;
- audioBuffer->size = 0;
-
- size_t framesReady = mProxy->framesReady();
-
- if (framesReady == 0) {
- cblk->lock.lock();
- goto start_loop_here;
- while (framesReady == 0) {
- active = mActive;
- if (CC_UNLIKELY(!active)) {
- cblk->lock.unlock();
- return NO_MORE_BUFFERS;
- }
- if (CC_UNLIKELY(!waitCount)) {
- cblk->lock.unlock();
- return WOULD_BLOCK;
- }
- if (!(cblk->flags & CBLK_INVALID)) {
- mLock.unlock();
- // this condition is in shared memory, so if IAudioRecord and control block
- // are replaced due to mediaserver death or IAudioRecord invalidation then
- // cv won't be signalled, but fortunately the timeout will limit the wait
- result = cblk->cv.waitRelative(cblk->lock, milliseconds(waitTimeMs));
- cblk->lock.unlock();
- mLock.lock();
- if (!mActive) {
- return status_t(STOPPED);
- }
- // IAudioRecord may have been re-created while mLock was unlocked
- cblk = mCblk;
- cblk->lock.lock();
- }
- if (cblk->flags & CBLK_INVALID) {
- goto create_new_record;
- }
- if (CC_UNLIKELY(result != NO_ERROR)) {
- cblk->waitTimeMs += waitTimeMs;
- if (cblk->waitTimeMs >= cblk->bufferTimeoutMs) {
- ALOGW( "obtainBuffer timed out (is the CPU pegged?) "
- "user=%08x, server=%08x", cblk->user, cblk->server);
- cblk->lock.unlock();
- // callback thread or sync event hasn't changed
- result = mAudioRecord->start(AudioSystem::SYNC_EVENT_SAME, 0);
- cblk->lock.lock();
- if (result == DEAD_OBJECT) {
- android_atomic_or(CBLK_INVALID, &cblk->flags);
-create_new_record:
- audio_track_cblk_t* temp = cblk;
- result = AudioRecord::restoreRecord_l(temp);
- cblk = temp;
- }
- if (result != NO_ERROR) {
- ALOGW("obtainBuffer create Track error %d", result);
- cblk->lock.unlock();
- return result;
+ const struct timespec *requested;
+ if (waitCount == -1) {
+ requested = &ClientProxy::kForever;
+ } else if (waitCount == 0) {
+ requested = &ClientProxy::kNonBlocking;
+ } else if (waitCount > 0) {
+ long long ms = WAIT_PERIOD_MS * (long long) waitCount;
+ struct timespec timeout;
+ timeout.tv_sec = ms / 1000;
+ timeout.tv_nsec = (int) (ms % 1000) * 1000000;
+ requested = &timeout;
+ } else {
+ ALOGE("%s invalid waitCount %d", __func__, waitCount);
+ requested = NULL;
+ }
+ return obtainBuffer(audioBuffer, requested);
+}
+
+status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,
+ struct timespec *elapsed, size_t *nonContig)
+{
+ // previous and new IAudioRecord sequence numbers are used to detect track re-creation
+ uint32_t oldSequence = 0;
+ uint32_t newSequence;
+
+ Proxy::Buffer buffer;
+ status_t status = NO_ERROR;
+
+ static const int32_t kMaxTries = 5;
+ int32_t tryCounter = kMaxTries;
+
+ do {
+ // obtainBuffer() is called with mutex unlocked, so keep extra references to these fields to
+ // keep them from going away if another thread re-creates the track during obtainBuffer()
+ sp<AudioRecordClientProxy> proxy;
+ sp<IMemory> iMem;
+ {
+ // start of lock scope
+ AutoMutex lock(mLock);
+
+ newSequence = mSequence;
+ // did previous obtainBuffer() fail due to media server death or voluntary invalidation?
+ if (status == DEAD_OBJECT) {
+ // re-create track, unless someone else has already done so
+ if (newSequence == oldSequence) {
+ status = restoreRecord_l("obtainBuffer");
+ if (status != NO_ERROR) {
+ break;
}
- cblk->waitTimeMs = 0;
- }
- if (--waitCount == 0) {
- cblk->lock.unlock();
- return TIMED_OUT;
}
}
- // read the server count again
- start_loop_here:
- framesReady = mProxy->framesReady();
- }
- cblk->lock.unlock();
- }
+ oldSequence = newSequence;
- cblk->waitTimeMs = 0;
- // reset time out to running value after obtaining a buffer
- cblk->bufferTimeoutMs = MAX_RUN_TIMEOUT_MS;
+ // Keep the extra references
+ proxy = mProxy;
+ iMem = mCblkMemory;
- if (framesReq > framesReady) {
- framesReq = framesReady;
- }
+ // Non-blocking if track is stopped
+ if (!mActive) {
+ requested = &ClientProxy::kNonBlocking;
+ }
- uint32_t u = cblk->user;
- uint32_t bufferEnd = cblk->userBase + mFrameCount;
+ } // end of lock scope
- if (framesReq > bufferEnd - u) {
- framesReq = bufferEnd - u;
- }
+ buffer.mFrameCount = audioBuffer->frameCount;
+ // FIXME starts the requested timeout and elapsed over from scratch
+ status = proxy->obtainBuffer(&buffer, requested, elapsed);
+
+ } while ((status == DEAD_OBJECT) && (tryCounter-- > 0));
- audioBuffer->frameCount = framesReq;
- audioBuffer->size = framesReq * mFrameSize;
- audioBuffer->raw = mProxy->buffer(u);
- active = mActive;
- return active ? status_t(NO_ERROR) : status_t(STOPPED);
+ audioBuffer->frameCount = buffer.mFrameCount;
+ audioBuffer->size = buffer.mFrameCount * mFrameSize;
+ audioBuffer->raw = buffer.mRaw;
+ if (nonContig != NULL) {
+ *nonContig = buffer.mNonContig;
+ }
+ return status;
}
void AudioRecord::releaseBuffer(Buffer* audioBuffer)
{
- ALOG_ASSERT(mStatus == NO_ERROR && mProxy != NULL);
+ // all TRANSFER_* are valid
+
+ size_t stepCount = audioBuffer->size / mFrameSize;
+ if (stepCount == 0) {
+ return;
+ }
+
+ Proxy::Buffer buffer;
+ buffer.mFrameCount = stepCount;
+ buffer.mRaw = audioBuffer->raw;
AutoMutex lock(mLock);
- (void) mProxy->stepUser(audioBuffer->frameCount);
+ mInOverrun = false;
+ mProxy->releaseBuffer(&buffer);
+
+ // the server does not automatically disable recorder on overrun, so no need to restart
}
audio_io_handle_t AudioRecord::getInput() const
@@ -605,226 +649,324 @@ audio_io_handle_t AudioRecord::getInput() const
return mInput;
}
-// must be called with mLock held
-audio_io_handle_t AudioRecord::getInput_l()
-{
- mInput = AudioSystem::getInput(mInputSource,
- mSampleRate,
- mFormat,
- mChannelMask,
- mSessionId);
- return mInput;
-}
-
-int AudioRecord::getSessionId() const
-{
- // no lock needed because session ID doesn't change after first set()
- return mSessionId;
-}
-
// -------------------------------------------------------------------------
ssize_t AudioRecord::read(void* buffer, size_t userSize)
{
- ssize_t read = 0;
- Buffer audioBuffer;
- int8_t *dst = static_cast<int8_t*>(buffer);
+ if (mTransfer != TRANSFER_SYNC) {
+ return INVALID_OPERATION;
+ }
- if (ssize_t(userSize) < 0) {
- // sanity-check. user is most-likely passing an error code.
- ALOGE("AudioRecord::read(buffer=%p, size=%u (%d)",
- buffer, userSize, userSize);
+ if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) {
+ // sanity-check. user is most-likely passing an error code, and it would
+ // make the return value ambiguous (actualSize vs error).
+ ALOGE("AudioRecord::read(buffer=%p, size=%u (%d)", buffer, userSize, userSize);
return BAD_VALUE;
}
- mLock.lock();
- // acquire a strong reference on the IAudioRecord and IMemory so that they cannot be destroyed
- // while we are accessing the cblk
- sp<IAudioRecord> audioRecord = mAudioRecord;
- sp<IMemory> iMem = mCblkMemory;
- mLock.unlock();
-
- do {
+ ssize_t read = 0;
+ Buffer audioBuffer;
- audioBuffer.frameCount = userSize/frameSize();
+ while (userSize >= mFrameSize) {
+ audioBuffer.frameCount = userSize / mFrameSize;
- // By using a wait count corresponding to twice the timeout period in
- // obtainBuffer() we give a chance to recover once for a read timeout
- // (if media_server crashed for instance) before returning a length of
- // 0 bytes read to the client
- status_t err = obtainBuffer(&audioBuffer, ((2 * MAX_RUN_TIMEOUT_MS) / WAIT_PERIOD_MS));
+ status_t err = obtainBuffer(&audioBuffer, &ClientProxy::kForever);
if (err < 0) {
- // out of buffers, return #bytes written
- if (err == status_t(NO_MORE_BUFFERS)) {
+ if (read > 0) {
break;
}
- if (err == status_t(TIMED_OUT)) {
- // return partial transfer count
- return read;
- }
return ssize_t(err);
}
size_t bytesRead = audioBuffer.size;
- memcpy(dst, audioBuffer.i8, bytesRead);
-
- dst += bytesRead;
+ memcpy(buffer, audioBuffer.i8, bytesRead);
+ buffer = ((char *) buffer) + bytesRead;
userSize -= bytesRead;
read += bytesRead;
releaseBuffer(&audioBuffer);
- } while (userSize);
+ }
return read;
}
// -------------------------------------------------------------------------
-bool AudioRecord::processAudioBuffer(const sp<AudioRecordThread>& thread)
+nsecs_t AudioRecord::processAudioBuffer(const sp<AudioRecordThread>& thread)
{
- Buffer audioBuffer;
- uint32_t frames = mRemainingFrames;
- size_t readSize;
-
mLock.lock();
- // acquire a strong reference on the IAudioRecord and IMemory so that they cannot be destroyed
- // while we are accessing the cblk
- sp<IAudioRecord> audioRecord = mAudioRecord;
- sp<IMemory> iMem = mCblkMemory;
- audio_track_cblk_t* cblk = mCblk;
+ if (mAwaitBoost) {
+ mAwaitBoost = false;
+ mLock.unlock();
+ static const int32_t kMaxTries = 5;
+ int32_t tryCounter = kMaxTries;
+ uint32_t pollUs = 10000;
+ do {
+ int policy = sched_getscheduler(0);
+ if (policy == SCHED_FIFO || policy == SCHED_RR) {
+ break;
+ }
+ usleep(pollUs);
+ pollUs <<= 1;
+ } while (tryCounter-- > 0);
+ if (tryCounter < 0) {
+ ALOGE("did not receive expected priority boost on time");
+ }
+ // Run again immediately
+ return 0;
+ }
+
+ // Can only reference mCblk while locked
+ int32_t flags = android_atomic_and(~CBLK_OVERRUN, &mCblk->mFlags);
+
+ // Check for track invalidation
+ if (flags & CBLK_INVALID) {
+ (void) restoreRecord_l("processAudioBuffer");
+ mLock.unlock();
+ // Run again immediately, but with a new IAudioRecord
+ return 0;
+ }
+
bool active = mActive;
- uint32_t markerPosition = mMarkerPosition;
- uint32_t newPosition = mNewPosition;
- uint32_t user = cblk->user;
- // determine whether a marker callback will be needed, while locked
- bool needMarker = !mMarkerReached && (mMarkerPosition > 0) && (user >= mMarkerPosition);
- if (needMarker) {
- mMarkerReached = true;
- }
- // determine the number of new position callback(s) that will be needed, while locked
+
+ // Manage overrun callback, must be done under lock to avoid race with releaseBuffer()
+ bool newOverrun = false;
+ if (flags & CBLK_OVERRUN) {
+ if (!mInOverrun) {
+ mInOverrun = true;
+ newOverrun = true;
+ }
+ }
+
+ // Get current position of server
+ size_t position = mProxy->getPosition();
+
+ // Manage marker callback
+ bool markerReached = false;
+ size_t markerPosition = mMarkerPosition;
+ // FIXME fails for wraparound, need 64 bits
+ if (!mMarkerReached && (markerPosition > 0) && (position >= markerPosition)) {
+ mMarkerReached = markerReached = true;
+ }
+
+ // Determine the number of new position callback(s) that will be needed, while locked
+ size_t newPosCount = 0;
+ size_t newPosition = mNewPosition;
uint32_t updatePeriod = mUpdatePeriod;
- uint32_t needNewPos = updatePeriod > 0 && user >= newPosition ?
- ((user - newPosition) / updatePeriod) + 1 : 0;
- mNewPosition = newPosition + updatePeriod * needNewPos;
+ // FIXME fails for wraparound, need 64 bits
+ if (updatePeriod > 0 && position >= newPosition) {
+ newPosCount = ((position - newPosition) / updatePeriod) + 1;
+ mNewPosition += updatePeriod * newPosCount;
+ }
+
+ // Cache other fields that will be needed soon
+ size_t notificationFrames = mNotificationFramesAct;
+ if (mRefreshRemaining) {
+ mRefreshRemaining = false;
+ mRemainingFrames = notificationFrames;
+ mRetryOnPartialBuffer = false;
+ }
+ size_t misalignment = mProxy->getMisalignment();
+ int32_t sequence = mSequence;
+
+ // These fields don't need to be cached, because they are assigned only by set():
+ // mTransfer, mCbf, mUserData, mSampleRate
+
mLock.unlock();
- // perform marker callback, while unlocked
- if (needMarker) {
+ // perform callbacks while unlocked
+ if (newOverrun) {
+ mCbf(EVENT_OVERRUN, mUserData, NULL);
+ }
+ if (markerReached) {
mCbf(EVENT_MARKER, mUserData, &markerPosition);
}
-
- // perform new position callback(s), while unlocked
- for (; needNewPos > 0; --needNewPos) {
- uint32_t temp = newPosition;
+ while (newPosCount > 0) {
+ size_t temp = newPosition;
mCbf(EVENT_NEW_POS, mUserData, &temp);
newPosition += updatePeriod;
+ newPosCount--;
+ }
+ if (mObservedSequence != sequence) {
+ mObservedSequence = sequence;
+ mCbf(EVENT_NEW_IAUDIORECORD, mUserData, NULL);
}
- do {
- audioBuffer.frameCount = frames;
- // Calling obtainBuffer() with a wait count of 1
- // limits wait time to WAIT_PERIOD_MS. This prevents from being
- // stuck here not being able to handle timed events (position, markers).
- status_t err = obtainBuffer(&audioBuffer, 1);
- if (err < NO_ERROR) {
- if (err != TIMED_OUT) {
- ALOGE_IF(err != status_t(NO_MORE_BUFFERS),
- "Error obtaining an audio buffer, giving up.");
- return false;
+ // if inactive, then don't run me again until re-started
+ if (!active) {
+ return NS_INACTIVE;
+ }
+
+ // Compute the estimated time until the next timed event (position, markers)
+ uint32_t minFrames = ~0;
+ if (!markerReached && position < markerPosition) {
+ minFrames = markerPosition - position;
+ }
+ if (updatePeriod > 0 && updatePeriod < minFrames) {
+ minFrames = updatePeriod;
+ }
+
+ // If > 0, poll periodically to recover from a stuck server. A good value is 2.
+ static const uint32_t kPoll = 0;
+ if (kPoll > 0 && mTransfer == TRANSFER_CALLBACK && kPoll * notificationFrames < minFrames) {
+ minFrames = kPoll * notificationFrames;
+ }
+
+ // Convert frame units to time units
+ nsecs_t ns = NS_WHENEVER;
+ if (minFrames != (uint32_t) ~0) {
+ // This "fudge factor" avoids soaking CPU, and compensates for late progress by server
+ static const nsecs_t kFudgeNs = 10000000LL; // 10 ms
+ ns = ((minFrames * 1000000000LL) / mSampleRate) + kFudgeNs;
+ }
+
+ // If not supplying data by EVENT_MORE_DATA, then we're done
+ if (mTransfer != TRANSFER_CALLBACK) {
+ return ns;
+ }
+
+ struct timespec timeout;
+ const struct timespec *requested = &ClientProxy::kForever;
+ if (ns != NS_WHENEVER) {
+ timeout.tv_sec = ns / 1000000000LL;
+ timeout.tv_nsec = ns % 1000000000LL;
+ ALOGV("timeout %ld.%03d", timeout.tv_sec, (int) timeout.tv_nsec / 1000000);
+ requested = &timeout;
+ }
+
+ while (mRemainingFrames > 0) {
+
+ Buffer audioBuffer;
+ audioBuffer.frameCount = mRemainingFrames;
+ size_t nonContig;
+ status_t err = obtainBuffer(&audioBuffer, requested, NULL, &nonContig);
+ LOG_ALWAYS_FATAL_IF((err != NO_ERROR) != (audioBuffer.frameCount == 0),
+ "obtainBuffer() err=%d frameCount=%u", err, audioBuffer.frameCount);
+ requested = &ClientProxy::kNonBlocking;
+ size_t avail = audioBuffer.frameCount + nonContig;
+ ALOGV("obtainBuffer(%u) returned %u = %u + %u",
+ mRemainingFrames, avail, audioBuffer.frameCount, nonContig);
+ if (err != NO_ERROR) {
+ if (err == TIMED_OUT || err == WOULD_BLOCK || err == -EINTR) {
+ break;
+ }
+ ALOGE("Error %d obtaining an audio buffer, giving up.", err);
+ return NS_NEVER;
+ }
+
+ if (mRetryOnPartialBuffer) {
+ mRetryOnPartialBuffer = false;
+ if (avail < mRemainingFrames) {
+ int64_t myns = ((mRemainingFrames - avail) *
+ 1100000000LL) / mSampleRate;
+ if (ns < 0 || myns < ns) {
+ ns = myns;
+ }
+ return ns;
}
- break;
}
- if (err == status_t(STOPPED)) return false;
size_t reqSize = audioBuffer.size;
mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);
- readSize = audioBuffer.size;
+ size_t readSize = audioBuffer.size;
// Sanity check on returned size
- if (ssize_t(readSize) <= 0) {
- // The callback is done filling buffers
+ if (ssize_t(readSize) < 0 || readSize > reqSize) {
+ ALOGE("EVENT_MORE_DATA requested %u bytes but callback returned %d bytes",
+ reqSize, (int) readSize);
+ return NS_NEVER;
+ }
+
+ if (readSize == 0) {
+ // The callback is done consuming buffers
// Keep this thread going to handle timed events and
- // still try to get more data in intervals of WAIT_PERIOD_MS
+ // still try to provide more data in intervals of WAIT_PERIOD_MS
// but don't just loop and block the CPU, so wait
- usleep(WAIT_PERIOD_MS*1000);
- break;
+ return WAIT_PERIOD_MS * 1000000LL;
}
- if (readSize > reqSize) readSize = reqSize;
- audioBuffer.size = readSize;
- audioBuffer.frameCount = readSize/frameSize();
- frames -= audioBuffer.frameCount;
+ size_t releasedFrames = readSize / mFrameSize;
+ audioBuffer.frameCount = releasedFrames;
+ mRemainingFrames -= releasedFrames;
+ if (misalignment >= releasedFrames) {
+ misalignment -= releasedFrames;
+ } else {
+ misalignment = 0;
+ }
releaseBuffer(&audioBuffer);
- } while (frames);
+ // FIXME here is where we would repeat EVENT_MORE_DATA again on same advanced buffer
+ // if callback doesn't like to accept the full chunk
+ if (readSize < reqSize) {
+ continue;
+ }
+ // There could be enough non-contiguous frames available to satisfy the remaining request
+ if (mRemainingFrames <= nonContig) {
+ continue;
+ }
- // Manage overrun callback
- if (active && (mProxy->framesAvailable() == 0)) {
- // The value of active is stale, but we are almost sure to be active here because
- // otherwise we would have exited when obtainBuffer returned STOPPED earlier.
- ALOGV("Overrun user: %x, server: %x, flags %04x", cblk->user, cblk->server, cblk->flags);
- if (!(android_atomic_or(CBLK_UNDERRUN, &cblk->flags) & CBLK_UNDERRUN)) {
- mCbf(EVENT_OVERRUN, mUserData, NULL);
+#if 0
+ // This heuristic tries to collapse a series of EVENT_MORE_DATA that would total to a
+ // sum <= notificationFrames. It replaces that series by at most two EVENT_MORE_DATA
+ // that total to a sum == notificationFrames.
+ if (0 < misalignment && misalignment <= mRemainingFrames) {
+ mRemainingFrames = misalignment;
+ return (mRemainingFrames * 1100000000LL) / mSampleRate;
}
- }
+#endif
- if (frames == 0) {
- mRemainingFrames = mNotificationFrames;
- } else {
- mRemainingFrames = frames;
}
- return true;
+ mRemainingFrames = notificationFrames;
+ mRetryOnPartialBuffer = true;
+
+ // A lot has transpired since ns was calculated, so run again immediately and re-calculate
+ return 0;
}
-// must be called with mLock and cblk.lock held. Callers must also hold strong references on
-// the IAudioRecord and IMemory in case they are recreated here.
-// If the IAudioRecord is successfully restored, the cblk pointer is updated
-status_t AudioRecord::restoreRecord_l(audio_track_cblk_t*& refCblk)
+status_t AudioRecord::restoreRecord_l(const char *from)
{
+ ALOGW("dead IAudioRecord, creating a new one from %s()", from);
+ ++mSequence;
status_t result;
- audio_track_cblk_t* cblk = refCblk;
- audio_track_cblk_t* newCblk = cblk;
- ALOGW("dead IAudioRecord, creating a new one");
-
- // signal old cblk condition so that other threads waiting for available buffers stop
- // waiting now
- cblk->cv.broadcast();
- cblk->lock.unlock();
-
// if the new IAudioRecord is created, openRecord_l() will modify the
// following member variables: mAudioRecord, mCblkMemory and mCblk.
// It will also delete the strong references on previous IAudioRecord and IMemory
- result = openRecord_l(mSampleRate, mFormat, mFrameCount, getInput_l());
+ size_t position = mProxy->getPosition();
+ mNewPosition = position + mUpdatePeriod;
+ result = openRecord_l(position);
if (result == NO_ERROR) {
- newCblk = mCblk;
- // callback thread or sync event hasn't changed
- result = mAudioRecord->start(AudioSystem::SYNC_EVENT_SAME, 0);
+ if (mActive) {
+ // callback thread or sync event hasn't changed
+ // FIXME this fails if we have a new AudioFlinger instance
+ result = mAudioRecord->start(AudioSystem::SYNC_EVENT_SAME, 0);
+ }
}
if (result != NO_ERROR) {
+ ALOGW("restoreRecord_l() failed status %d", result);
mActive = false;
}
- ALOGV("restoreRecord_l() status %d mActive %d cblk %p, old cblk %p flags %08x old flags %08x",
- result, mActive, newCblk, cblk, newCblk->flags, cblk->flags);
-
- if (result == NO_ERROR) {
- // from now on we switch to the newly created cblk
- refCblk = newCblk;
- }
- newCblk->lock.lock();
+ return result;
+}
- ALOGW_IF(result != NO_ERROR, "restoreRecord_l() error %d", result);
+// =========================================================================
- return result;
+void AudioRecord::DeathNotifier::binderDied(const wp<IBinder>& who)
+{
+ sp<AudioRecord> audioRecord = mAudioRecord.promote();
+ if (audioRecord != 0) {
+ AutoMutex lock(audioRecord->mLock);
+ audioRecord->mProxy->binderDied();
+ }
}
// =========================================================================
AudioRecord::AudioRecordThread::AudioRecordThread(AudioRecord& receiver, bool bCanCallJava)
- : Thread(bCanCallJava), mReceiver(receiver), mPaused(true)
+ : Thread(bCanCallJava), mReceiver(receiver), mPaused(true), mPausedInt(false), mPausedNs(0LL)
{
}
@@ -841,18 +983,46 @@ bool AudioRecord::AudioRecordThread::threadLoop()
// caller will check for exitPending()
return true;
}
+ if (mPausedInt) {
+ if (mPausedNs > 0) {
+ (void) mMyCond.waitRelative(mMyLock, mPausedNs);
+ } else {
+ mMyCond.wait(mMyLock);
+ }
+ mPausedInt = false;
+ return true;
+ }
}
- if (!mReceiver.processAudioBuffer(this)) {
- pause();
+ nsecs_t ns = mReceiver.processAudioBuffer(this);
+ switch (ns) {
+ case 0:
+ return true;
+ case NS_INACTIVE:
+ pauseInternal();
+ return true;
+ case NS_NEVER:
+ return false;
+ case NS_WHENEVER:
+ // FIXME increase poll interval, or make event-driven
+ ns = 1000000000LL;
+ // fall through
+ default:
+ LOG_ALWAYS_FATAL_IF(ns < 0, "processAudioBuffer() returned %lld", ns);
+ pauseInternal(ns);
+ return true;
}
- return true;
}
void AudioRecord::AudioRecordThread::requestExit()
{
// must be in this order to avoid a race condition
Thread::requestExit();
- resume();
+ AutoMutex _l(mMyLock);
+ if (mPaused || mPausedInt) {
+ mPaused = false;
+ mPausedInt = false;
+ mMyCond.signal();
+ }
}
void AudioRecord::AudioRecordThread::pause()
@@ -864,12 +1034,20 @@ void AudioRecord::AudioRecordThread::pause()
void AudioRecord::AudioRecordThread::resume()
{
AutoMutex _l(mMyLock);
- if (mPaused) {
+ if (mPaused || mPausedInt) {
mPaused = false;
+ mPausedInt = false;
mMyCond.signal();
}
}
+void AudioRecord::AudioRecordThread::pauseInternal(nsecs_t ns)
+{
+ AutoMutex _l(mMyLock);
+ mPausedInt = true;
+ mPausedNs = ns;
+}
+
// -------------------------------------------------------------------------
}; // namespace android
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index 693df60..8033c2c 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -20,6 +20,7 @@
#include <utils/Log.h>
#include <binder/IServiceManager.h>
#include <media/AudioSystem.h>
+#include <media/IAudioFlinger.h>
#include <media/IAudioPolicyService.h>
#include <math.h>
@@ -75,6 +76,14 @@ const sp<IAudioFlinger>& AudioSystem::get_audio_flinger()
return gAudioFlinger;
}
+/* static */ status_t AudioSystem::checkAudioFlinger()
+{
+ if (defaultServiceManager()->checkService(String16("media.audio_flinger")) != 0) {
+ return NO_ERROR;
+ }
+ return DEAD_OBJECT;
+}
+
status_t AudioSystem::muteMicrophone(bool state) {
const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
if (af == 0) return PERMISSION_DENIED;
@@ -361,8 +370,8 @@ status_t AudioSystem::setVoiceVolume(float value)
return af->setVoiceVolume(value);
}
-status_t AudioSystem::getRenderPosition(size_t *halFrames, size_t *dspFrames,
- audio_stream_type_t stream)
+status_t AudioSystem::getRenderPosition(audio_io_handle_t output, size_t *halFrames,
+ size_t *dspFrames, audio_stream_type_t stream)
{
const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
if (af == 0) return PERMISSION_DENIED;
@@ -371,7 +380,11 @@ status_t AudioSystem::getRenderPosition(size_t *halFrames, size_t *dspFrames,
stream = AUDIO_STREAM_MUSIC;
}
- return af->getRenderPosition(halFrames, dspFrames, getOutput(stream));
+ if (output == 0) {
+ output = getOutput(stream);
+ }
+
+ return af->getRenderPosition(halFrames, dspFrames, output);
}
size_t AudioSystem::getInputFramesLost(audio_io_handle_t ioHandle) {
@@ -442,14 +455,14 @@ void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, audio_io_handle
OutputDescriptor *outputDesc = new OutputDescriptor(*desc);
gOutputs.add(ioHandle, outputDesc);
- ALOGV("ioConfigChanged() new output samplingRate %u, format %d channels %#x frameCount %u "
+ ALOGV("ioConfigChanged() new output samplingRate %u, format %d channel mask %#x frameCount %u "
"latency %d",
- outputDesc->samplingRate, outputDesc->format, outputDesc->channels,
+ outputDesc->samplingRate, outputDesc->format, outputDesc->channelMask,
outputDesc->frameCount, outputDesc->latency);
} break;
case OUTPUT_CLOSED: {
if (gOutputs.indexOfKey(ioHandle) < 0) {
- ALOGW("ioConfigChanged() closing unknow output! %d", ioHandle);
+ ALOGW("ioConfigChanged() closing unknown output! %d", ioHandle);
break;
}
ALOGV("ioConfigChanged() output %d closed", ioHandle);
@@ -460,16 +473,16 @@ void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, audio_io_handle
case OUTPUT_CONFIG_CHANGED: {
int index = gOutputs.indexOfKey(ioHandle);
if (index < 0) {
- ALOGW("ioConfigChanged() modifying unknow output! %d", ioHandle);
+ ALOGW("ioConfigChanged() modifying unknown output! %d", ioHandle);
break;
}
if (param2 == NULL) break;
desc = (const OutputDescriptor *)param2;
- ALOGV("ioConfigChanged() new config for output %d samplingRate %u, format %d channels %#x "
+ ALOGV("ioConfigChanged() new config for output %d samplingRate %u, format %d channel mask %#x "
"frameCount %d latency %d",
ioHandle, desc->samplingRate, desc->format,
- desc->channels, desc->frameCount, desc->latency);
+ desc->channelMask, desc->frameCount, desc->latency);
OutputDescriptor *outputDesc = gOutputs.valueAt(index);
delete outputDesc;
outputDesc = new OutputDescriptor(*desc);
@@ -532,6 +545,8 @@ const sp<IAudioPolicyService>& AudioSystem::get_audio_policy_service()
return gAudioPolicyService;
}
+// ---------------------------------------------------------------------------
+
status_t AudioSystem::setDeviceConnectionState(audio_devices_t device,
audio_policy_dev_state_t state,
const char *device_address)
@@ -585,11 +600,12 @@ audio_io_handle_t AudioSystem::getOutput(audio_stream_type_t stream,
uint32_t samplingRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- audio_output_flags_t flags)
+ audio_output_flags_t flags,
+ const audio_offload_info_t *offloadInfo)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return 0;
- return aps->getOutput(stream, samplingRate, format, channelMask, flags);
+ return aps->getOutput(stream, samplingRate, format, channelMask, flags, offloadInfo);
}
status_t AudioSystem::startOutput(audio_io_handle_t output,
@@ -764,6 +780,13 @@ size_t AudioSystem::getPrimaryOutputFrameCount()
return af->getPrimaryOutputFrameCount();
}
+status_t AudioSystem::setLowRamDevice(bool isLowRamDevice)
+{
+ const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
+ if (af == 0) return PERMISSION_DENIED;
+ return af->setLowRamDevice(isLowRamDevice);
+}
+
void AudioSystem::clearAudioConfigCache()
{
Mutex::Autolock _l(gLock);
@@ -771,6 +794,14 @@ void AudioSystem::clearAudioConfigCache()
gOutputs.clear();
}
+bool AudioSystem::isOffloadSupported(const audio_offload_info_t& info)
+{
+ ALOGV("isOffloadSupported()");
+ const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
+ if (aps == 0) return false;
+ return aps->isOffloadSupported(info);
+}
+
// ---------------------------------------------------------------------------
void AudioSystem::AudioPolicyServiceClient::binderDied(const wp<IBinder>& who) {
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 7eeb4f8..fe5cd9e 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -19,31 +19,17 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioTrack"
-#include <stdint.h>
-#include <sys/types.h>
-#include <limits.h>
-
-#include <sched.h>
#include <sys/resource.h>
-
-#include <private/media/AudioTrackShared.h>
-
-#include <media/AudioSystem.h>
+#include <audio_utils/primitives.h>
+#include <binder/IPCThreadState.h>
#include <media/AudioTrack.h>
-
#include <utils/Log.h>
-#include <binder/Parcel.h>
-#include <binder/IPCThreadState.h>
-#include <utils/Timers.h>
-#include <utils/Atomic.h>
-
-#include <cutils/bitops.h>
-#include <cutils/compiler.h>
+#include <private/media/AudioTrackShared.h>
+#include <media/IAudioFlinger.h>
-#include <system/audio.h>
-#include <system/audio_policy.h>
+#define WAIT_PERIOD_MS 10
+#define WAIT_STREAM_END_TIMEOUT_SEC 120
-#include <audio_utils/primitives.h>
namespace android {
// ---------------------------------------------------------------------------
@@ -82,7 +68,9 @@ status_t AudioTrack::getMinFrameCount(
// Ensure that buffer depth covers at least audio hardware latency
uint32_t minBufCount = afLatency / ((1000 * afFrameCount) / afSampleRate);
- if (minBufCount < 2) minBufCount = 2;
+ if (minBufCount < 2) {
+ minBufCount = 2;
+ }
*frameCount = (sampleRate == 0) ? afFrameCount * minBufCount :
afFrameCount * minBufCount * sampleRate / afSampleRate;
@@ -97,8 +85,7 @@ AudioTrack::AudioTrack()
: mStatus(NO_INIT),
mIsTimed(false),
mPreviousPriority(ANDROID_PRIORITY_NORMAL),
- mPreviousSchedulingGroup(SP_DEFAULT),
- mProxy(NULL)
+ mPreviousSchedulingGroup(SP_DEFAULT)
{
}
@@ -112,16 +99,17 @@ AudioTrack::AudioTrack(
callback_t cbf,
void* user,
int notificationFrames,
- int sessionId)
+ int sessionId,
+ transfer_type transferType,
+ const audio_offload_info_t *offloadInfo)
: mStatus(NO_INIT),
mIsTimed(false),
mPreviousPriority(ANDROID_PRIORITY_NORMAL),
- mPreviousSchedulingGroup(SP_DEFAULT),
- mProxy(NULL)
+ mPreviousSchedulingGroup(SP_DEFAULT)
{
mStatus = set(streamType, sampleRate, format, channelMask,
frameCount, flags, cbf, user, notificationFrames,
- 0 /*sharedBuffer*/, false /*threadCanCallJava*/, sessionId);
+ 0 /*sharedBuffer*/, false /*threadCanCallJava*/, sessionId, transferType, offloadInfo);
}
AudioTrack::AudioTrack(
@@ -134,42 +122,37 @@ AudioTrack::AudioTrack(
callback_t cbf,
void* user,
int notificationFrames,
- int sessionId)
+ int sessionId,
+ transfer_type transferType,
+ const audio_offload_info_t *offloadInfo)
: mStatus(NO_INIT),
mIsTimed(false),
mPreviousPriority(ANDROID_PRIORITY_NORMAL),
- mPreviousSchedulingGroup(SP_DEFAULT),
- mProxy(NULL)
+ mPreviousSchedulingGroup(SP_DEFAULT)
{
- if (sharedBuffer == 0) {
- ALOGE("sharedBuffer must be non-0");
- mStatus = BAD_VALUE;
- return;
- }
mStatus = set(streamType, sampleRate, format, channelMask,
0 /*frameCount*/, flags, cbf, user, notificationFrames,
- sharedBuffer, false /*threadCanCallJava*/, sessionId);
+ sharedBuffer, false /*threadCanCallJava*/, sessionId, transferType, offloadInfo);
}
AudioTrack::~AudioTrack()
{
- ALOGV_IF(mSharedBuffer != 0, "Destructor sharedBuffer: %p", mSharedBuffer->pointer());
-
if (mStatus == NO_ERROR) {
// Make sure that callback function exits in the case where
// it is looping on buffer full condition in obtainBuffer().
// Otherwise the callback thread will never exit.
stop();
if (mAudioTrackThread != 0) {
+ mProxy->interrupt();
mAudioTrackThread->requestExit(); // see comment in AudioTrack.h
mAudioTrackThread->requestExitAndWait();
mAudioTrackThread.clear();
}
+ mAudioTrack->asBinder()->unlinkToDeath(mDeathNotifier, this);
mAudioTrack.clear();
IPCThreadState::self()->flushCommands();
AudioSystem::releaseAudioSessionId(mSessionId);
}
- delete mProxy;
}
status_t AudioTrack::set(
@@ -184,8 +167,45 @@ status_t AudioTrack::set(
int notificationFrames,
const sp<IMemory>& sharedBuffer,
bool threadCanCallJava,
- int sessionId)
+ int sessionId,
+ transfer_type transferType,
+ const audio_offload_info_t *offloadInfo)
{
+ switch (transferType) {
+ case TRANSFER_DEFAULT:
+ if (sharedBuffer != 0) {
+ transferType = TRANSFER_SHARED;
+ } else if (cbf == NULL || threadCanCallJava) {
+ transferType = TRANSFER_SYNC;
+ } else {
+ transferType = TRANSFER_CALLBACK;
+ }
+ break;
+ case TRANSFER_CALLBACK:
+ if (cbf == NULL || sharedBuffer != 0) {
+ ALOGE("Transfer type TRANSFER_CALLBACK but cbf == NULL || sharedBuffer != 0");
+ return BAD_VALUE;
+ }
+ break;
+ case TRANSFER_OBTAIN:
+ case TRANSFER_SYNC:
+ if (sharedBuffer != 0) {
+ ALOGE("Transfer type TRANSFER_OBTAIN but sharedBuffer != 0");
+ return BAD_VALUE;
+ }
+ break;
+ case TRANSFER_SHARED:
+ if (sharedBuffer == 0) {
+ ALOGE("Transfer type TRANSFER_SHARED but sharedBuffer == 0");
+ return BAD_VALUE;
+ }
+ break;
+ default:
+ ALOGE("Invalid transfer type %d", transferType);
+ return BAD_VALUE;
+ }
+ mTransfer = transferType;
+
// FIXME "int" here is legacy and will be replaced by size_t later
if (frameCountInt < 0) {
ALOGE("Invalid frame count %d", frameCountInt);
@@ -199,11 +219,15 @@ status_t AudioTrack::set(
ALOGV("set() streamType %d frameCount %u flags %04x", streamType, frameCount, flags);
AutoMutex lock(mLock);
+
+ // invariant that mAudioTrack != 0 is true only after set() returns successfully
if (mAudioTrack != 0) {
ALOGE("Track already in use");
return INVALID_OPERATION;
}
+ mOutput = 0;
+
// handle default values first.
if (streamType == AUDIO_STREAM_DEFAULT) {
streamType = AUDIO_STREAM_MUSIC;
@@ -228,7 +252,7 @@ status_t AudioTrack::set(
// validate parameters
if (!audio_is_valid_format(format)) {
- ALOGE("Invalid format");
+ ALOGE("Invalid format %d", format);
return BAD_VALUE;
}
@@ -239,7 +263,12 @@ status_t AudioTrack::set(
}
// force direct flag if format is not linear PCM
- if (!audio_is_linear_pcm(format)) {
+ // or offload was requested
+ if ((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD)
+ || !audio_is_linear_pcm(format)) {
+ ALOGV( (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD)
+ ? "Offload request, forcing to Direct Output"
+ : "Not linear PCM, forcing to Direct Output");
flags = (audio_output_flags_t)
// FIXME why can't we allow direct AND fast?
((flags | AUDIO_OUTPUT_FLAG_DIRECT) & ~AUDIO_OUTPUT_FLAG_FAST);
@@ -268,7 +297,8 @@ status_t AudioTrack::set(
audio_io_handle_t output = AudioSystem::getOutput(
streamType,
sampleRate, format, channelMask,
- flags);
+ flags,
+ offloadInfo);
if (output == 0) {
ALOGE("Could not get audio output for stream type %d", streamType);
@@ -281,6 +311,7 @@ status_t AudioTrack::set(
mFrameCount = frameCount;
mReqFrameCount = frameCount;
mNotificationFramesReq = notificationFrames;
+ mNotificationFramesAct = 0;
mSessionId = sessionId;
mAuxEffectId = 0;
mFlags = flags;
@@ -298,178 +329,201 @@ status_t AudioTrack::set(
frameCount,
flags,
sharedBuffer,
- output);
+ output,
+ 0 /*epoch*/);
if (status != NO_ERROR) {
if (mAudioTrackThread != 0) {
- mAudioTrackThread->requestExit();
+ mAudioTrackThread->requestExit(); // see comment in AudioTrack.h
+ mAudioTrackThread->requestExitAndWait();
mAudioTrackThread.clear();
}
+ //Use of direct and offloaded output streams is ref counted by audio policy manager.
+ // As getOutput was called above and resulted in an output stream to be opened,
+ // we need to release it.
+ AudioSystem::releaseOutput(output);
return status;
}
mStatus = NO_ERROR;
-
mStreamType = streamType;
mFormat = format;
-
mSharedBuffer = sharedBuffer;
- mActive = false;
+ mState = STATE_STOPPED;
mUserData = user;
- mLoopCount = 0;
+ mLoopPeriod = 0;
mMarkerPosition = 0;
mMarkerReached = false;
mNewPosition = 0;
mUpdatePeriod = 0;
- mFlushed = false;
AudioSystem::acquireAudioSessionId(mSessionId);
+ mSequence = 1;
+ mObservedSequence = mSequence;
+ mInUnderrun = false;
+ mOutput = output;
+
return NO_ERROR;
}
// -------------------------------------------------------------------------
-void AudioTrack::start()
+status_t AudioTrack::start()
{
- sp<AudioTrackThread> t = mAudioTrackThread;
+ AutoMutex lock(mLock);
- ALOGV("start %p", this);
+ if (mState == STATE_ACTIVE) {
+ return INVALID_OPERATION;
+ }
- AutoMutex lock(mLock);
- // acquire a strong reference on the IMemory and IAudioTrack so that they cannot be destroyed
- // while we are accessing the cblk
- sp<IAudioTrack> audioTrack = mAudioTrack;
- sp<IMemory> iMem = mCblkMemory;
- audio_track_cblk_t* cblk = mCblk;
+ mInUnderrun = true;
- if (!mActive) {
- mFlushed = false;
- mActive = true;
- mNewPosition = cblk->server + mUpdatePeriod;
- cblk->lock.lock();
- cblk->bufferTimeoutMs = MAX_STARTUP_TIMEOUT_MS;
- cblk->waitTimeMs = 0;
- android_atomic_and(~CBLK_DISABLED, &cblk->flags);
- if (t != 0) {
- t->resume();
+ State previousState = mState;
+ if (previousState == STATE_PAUSED_STOPPING) {
+ mState = STATE_STOPPING;
+ } else {
+ mState = STATE_ACTIVE;
+ }
+ if (previousState == STATE_STOPPED || previousState == STATE_FLUSHED) {
+ // reset current position as seen by client to 0
+ mProxy->setEpoch(mProxy->getEpoch() - mProxy->getPosition());
+ // force refresh of remaining frames by processAudioBuffer() as last
+ // write before stop could be partial.
+ mRefreshRemaining = true;
+ }
+ mNewPosition = mProxy->getPosition() + mUpdatePeriod;
+ int32_t flags = android_atomic_and(~CBLK_DISABLED, &mCblk->mFlags);
+
+ sp<AudioTrackThread> t = mAudioTrackThread;
+ if (t != 0) {
+ if (previousState == STATE_STOPPING) {
+ mProxy->interrupt();
} else {
- mPreviousPriority = getpriority(PRIO_PROCESS, 0);
- get_sched_policy(0, &mPreviousSchedulingGroup);
- androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
+ t->resume();
}
+ } else {
+ mPreviousPriority = getpriority(PRIO_PROCESS, 0);
+ get_sched_policy(0, &mPreviousSchedulingGroup);
+ androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
+ }
- ALOGV("start %p before lock cblk %p", this, cblk);
- status_t status = NO_ERROR;
- if (!(cblk->flags & CBLK_INVALID)) {
- cblk->lock.unlock();
- ALOGV("mAudioTrack->start()");
- status = mAudioTrack->start();
- cblk->lock.lock();
- if (status == DEAD_OBJECT) {
- android_atomic_or(CBLK_INVALID, &cblk->flags);
- }
- }
- if (cblk->flags & CBLK_INVALID) {
- audio_track_cblk_t* temp = cblk;
- status = restoreTrack_l(temp, true /*fromStart*/);
- cblk = temp;
+ status_t status = NO_ERROR;
+ if (!(flags & CBLK_INVALID)) {
+ status = mAudioTrack->start();
+ if (status == DEAD_OBJECT) {
+ flags |= CBLK_INVALID;
}
- cblk->lock.unlock();
- if (status != NO_ERROR) {
- ALOGV("start() failed");
- mActive = false;
- if (t != 0) {
+ }
+ if (flags & CBLK_INVALID) {
+ status = restoreTrack_l("start");
+ }
+
+ if (status != NO_ERROR) {
+ ALOGE("start() status %d", status);
+ mState = previousState;
+ if (t != 0) {
+ if (previousState != STATE_STOPPING) {
t->pause();
- } else {
- setpriority(PRIO_PROCESS, 0, mPreviousPriority);
- set_sched_policy(0, mPreviousSchedulingGroup);
}
+ } else {
+ setpriority(PRIO_PROCESS, 0, mPreviousPriority);
+ set_sched_policy(0, mPreviousSchedulingGroup);
}
}
+ return status;
}
void AudioTrack::stop()
{
- sp<AudioTrackThread> t = mAudioTrackThread;
+ AutoMutex lock(mLock);
+ // FIXME pause then stop should not be a nop
+ if (mState != STATE_ACTIVE) {
+ return;
+ }
- ALOGV("stop %p", this);
+ if (isOffloaded()) {
+ mState = STATE_STOPPING;
+ } else {
+ mState = STATE_STOPPED;
+ }
- AutoMutex lock(mLock);
- if (mActive) {
- mActive = false;
- mCblk->cv.signal();
- mAudioTrack->stop();
- // Cancel loops (If we are in the middle of a loop, playback
- // would not stop until loopCount reaches 0).
- setLoop_l(0, 0, 0);
- // the playback head position will reset to 0, so if a marker is set, we need
- // to activate it again
- mMarkerReached = false;
- // Force flush if a shared buffer is used otherwise audioflinger
- // will not stop before end of buffer is reached.
- // It may be needed to make sure that we stop playback, likely in case looping is on.
- if (mSharedBuffer != 0) {
- flush_l();
- }
- if (t != 0) {
+ mProxy->interrupt();
+ mAudioTrack->stop();
+ // the playback head position will reset to 0, so if a marker is set, we need
+ // to activate it again
+ mMarkerReached = false;
+#if 0
+ // Force flush if a shared buffer is used otherwise audioflinger
+ // will not stop before end of buffer is reached.
+ // It may be needed to make sure that we stop playback, likely in case looping is on.
+ if (mSharedBuffer != 0) {
+ flush_l();
+ }
+#endif
+
+ sp<AudioTrackThread> t = mAudioTrackThread;
+ if (t != 0) {
+ if (!isOffloaded()) {
t->pause();
- } else {
- setpriority(PRIO_PROCESS, 0, mPreviousPriority);
- set_sched_policy(0, mPreviousSchedulingGroup);
}
+ } else {
+ setpriority(PRIO_PROCESS, 0, mPreviousPriority);
+ set_sched_policy(0, mPreviousSchedulingGroup);
}
-
}
bool AudioTrack::stopped() const
{
AutoMutex lock(mLock);
- return stopped_l();
+ return mState != STATE_ACTIVE;
}
void AudioTrack::flush()
{
+ if (mSharedBuffer != 0) {
+ return;
+ }
AutoMutex lock(mLock);
- if (!mActive && mSharedBuffer == 0) {
- flush_l();
+ if (mState == STATE_ACTIVE || mState == STATE_FLUSHED) {
+ return;
}
+ flush_l();
}
void AudioTrack::flush_l()
{
- ALOGV("flush");
- ALOG_ASSERT(!mActive);
+ ALOG_ASSERT(mState != STATE_ACTIVE);
// clear playback marker and periodic update counter
mMarkerPosition = 0;
mMarkerReached = false;
mUpdatePeriod = 0;
+ mRefreshRemaining = true;
- mFlushed = true;
+ mState = STATE_FLUSHED;
+ if (isOffloaded()) {
+ mProxy->interrupt();
+ }
+ mProxy->flush();
mAudioTrack->flush();
- // Release AudioTrack callback thread in case it was waiting for new buffers
- // in AudioTrack::obtainBuffer()
- mCblk->cv.signal();
}
void AudioTrack::pause()
{
- ALOGV("pause");
AutoMutex lock(mLock);
- if (mActive) {
- mActive = false;
- mCblk->cv.signal();
- mAudioTrack->pause();
+ if (mState == STATE_ACTIVE) {
+ mState = STATE_PAUSED;
+ } else if (mState == STATE_STOPPING) {
+ mState = STATE_PAUSED_STOPPING;
+ } else {
+ return;
}
+ mProxy->interrupt();
+ mAudioTrack->pause();
}
status_t AudioTrack::setVolume(float left, float right)
{
- if (mStatus != NO_ERROR) {
- return mStatus;
- }
- ALOG_ASSERT(mProxy != NULL);
-
if (left < 0.0f || left > 1.0f || right < 0.0f || right > 1.0f) {
return BAD_VALUE;
}
@@ -480,6 +534,9 @@ status_t AudioTrack::setVolume(float left, float right)
mProxy->setVolumeLR((uint32_t(uint16_t(right * 0x1000)) << 16) | uint16_t(left * 0x1000));
+ if (isOffloaded()) {
+ mAudioTrack->signal();
+ }
return NO_ERROR;
}
@@ -490,18 +547,11 @@ status_t AudioTrack::setVolume(float volume)
status_t AudioTrack::setAuxEffectSendLevel(float level)
{
- ALOGV("setAuxEffectSendLevel(%f)", level);
-
- if (mStatus != NO_ERROR) {
- return mStatus;
- }
- ALOG_ASSERT(mProxy != NULL);
-
if (level < 0.0f || level > 1.0f) {
return BAD_VALUE;
}
- AutoMutex lock(mLock);
+ AutoMutex lock(mLock);
mSendLevel = level;
mProxy->setSendLevel(level);
@@ -511,18 +561,17 @@ status_t AudioTrack::setAuxEffectSendLevel(float level)
void AudioTrack::getAuxEffectSendLevel(float* level) const
{
if (level != NULL) {
- *level = mSendLevel;
+ *level = mSendLevel;
}
}
status_t AudioTrack::setSampleRate(uint32_t rate)
{
- uint32_t afSamplingRate;
-
- if (mIsTimed) {
+ if (mIsTimed || isOffloaded()) {
return INVALID_OPERATION;
}
+ uint32_t afSamplingRate;
if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) {
return NO_INIT;
}
@@ -550,58 +599,45 @@ uint32_t AudioTrack::getSampleRate() const
status_t AudioTrack::setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount)
{
- AutoMutex lock(mLock);
- return setLoop_l(loopStart, loopEnd, loopCount);
-}
-
-// must be called with mLock held
-status_t AudioTrack::setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCount)
-{
- if (mSharedBuffer == 0 || mIsTimed) {
+ if (mSharedBuffer == 0 || mIsTimed || isOffloaded()) {
return INVALID_OPERATION;
}
- audio_track_cblk_t* cblk = mCblk;
-
- Mutex::Autolock _l(cblk->lock);
-
if (loopCount == 0) {
- cblk->loopStart = UINT_MAX;
- cblk->loopEnd = UINT_MAX;
- cblk->loopCount = 0;
- mLoopCount = 0;
- return NO_ERROR;
- }
-
- if (loopStart >= loopEnd ||
- loopEnd - loopStart > mFrameCount ||
- cblk->server > loopStart) {
- ALOGE("setLoop invalid value: loopStart %d, loopEnd %d, loopCount %d, framecount %d, "
- "user %d", loopStart, loopEnd, loopCount, mFrameCount, cblk->user);
+ ;
+ } else if (loopCount >= -1 && loopStart < loopEnd && loopEnd <= mFrameCount &&
+ loopEnd - loopStart >= MIN_LOOP) {
+ ;
+ } else {
return BAD_VALUE;
}
- if ((mSharedBuffer != 0) && (loopEnd > mFrameCount)) {
- ALOGE("setLoop invalid value: loop markers beyond data: loopStart %d, loopEnd %d, "
- "framecount %d",
- loopStart, loopEnd, mFrameCount);
- return BAD_VALUE;
+ AutoMutex lock(mLock);
+ // See setPosition() regarding setting parameters such as loop points or position while active
+ if (mState == STATE_ACTIVE) {
+ return INVALID_OPERATION;
}
-
- cblk->loopStart = loopStart;
- cblk->loopEnd = loopEnd;
- cblk->loopCount = loopCount;
- mLoopCount = loopCount;
-
+ setLoop_l(loopStart, loopEnd, loopCount);
return NO_ERROR;
}
+void AudioTrack::setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCount)
+{
+ // FIXME If setting a loop also sets position to start of loop, then
+ // this is correct. Otherwise it should be removed.
+ mNewPosition = mProxy->getPosition() + mUpdatePeriod;
+ mLoopPeriod = loopCount != 0 ? loopEnd - loopStart : 0;
+ mStaticProxy->setLoop(loopStart, loopEnd, loopCount);
+}
+
status_t AudioTrack::setMarkerPosition(uint32_t marker)
{
- if (mCbf == NULL) {
+ // The only purpose of setting marker position is to get a callback
+ if (mCbf == NULL || isOffloaded()) {
return INVALID_OPERATION;
}
+ AutoMutex lock(mLock);
mMarkerPosition = marker;
mMarkerReached = false;
@@ -610,10 +646,14 @@ status_t AudioTrack::setMarkerPosition(uint32_t marker)
status_t AudioTrack::getMarkerPosition(uint32_t *marker) const
{
+ if (isOffloaded()) {
+ return INVALID_OPERATION;
+ }
if (marker == NULL) {
return BAD_VALUE;
}
+ AutoMutex lock(mLock);
*marker = mMarkerPosition;
return NO_ERROR;
@@ -621,24 +661,27 @@ status_t AudioTrack::getMarkerPosition(uint32_t *marker) const
status_t AudioTrack::setPositionUpdatePeriod(uint32_t updatePeriod)
{
- if (mCbf == NULL) {
+ // The only purpose of setting position update period is to get a callback
+ if (mCbf == NULL || isOffloaded()) {
return INVALID_OPERATION;
}
- uint32_t curPosition;
- getPosition(&curPosition);
- mNewPosition = curPosition + updatePeriod;
+ AutoMutex lock(mLock);
+ mNewPosition = mProxy->getPosition() + updatePeriod;
mUpdatePeriod = updatePeriod;
-
return NO_ERROR;
}
status_t AudioTrack::getPositionUpdatePeriod(uint32_t *updatePeriod) const
{
+ if (isOffloaded()) {
+ return INVALID_OPERATION;
+ }
if (updatePeriod == NULL) {
return BAD_VALUE;
}
+ AutoMutex lock(mLock);
*updatePeriod = mUpdatePeriod;
return NO_ERROR;
@@ -646,80 +689,108 @@ status_t AudioTrack::getPositionUpdatePeriod(uint32_t *updatePeriod) const
status_t AudioTrack::setPosition(uint32_t position)
{
- if (mSharedBuffer == 0 || mIsTimed) {
+ if (mSharedBuffer == 0 || mIsTimed || isOffloaded()) {
return INVALID_OPERATION;
}
+ if (position > mFrameCount) {
+ return BAD_VALUE;
+ }
AutoMutex lock(mLock);
-
- if (!stopped_l()) {
+ // Currently we require that the player is inactive before setting parameters such as position
+ // or loop points. Otherwise, there could be a race condition: the application could read the
+ // current position, compute a new position or loop parameters, and then set that position or
+ // loop parameters but it would do the "wrong" thing since the position has continued to advance
+ // in the mean time. If we ever provide a sequencer in server, we could allow a way for the app
+ // to specify how it wants to handle such scenarios.
+ if (mState == STATE_ACTIVE) {
return INVALID_OPERATION;
}
+ mNewPosition = mProxy->getPosition() + mUpdatePeriod;
+ mLoopPeriod = 0;
+ // FIXME Check whether loops and setting position are incompatible in old code.
+ // If we use setLoop for both purposes we lose the capability to set the position while looping.
+ mStaticProxy->setLoop(position, mFrameCount, 0);
- audio_track_cblk_t* cblk = mCblk;
- Mutex::Autolock _l(cblk->lock);
+ return NO_ERROR;
+}
- if (position > cblk->user) {
+status_t AudioTrack::getPosition(uint32_t *position) const
+{
+ if (position == NULL) {
return BAD_VALUE;
}
- cblk->server = position;
- android_atomic_or(CBLK_FORCEREADY, &cblk->flags);
+ AutoMutex lock(mLock);
+ if (isOffloaded()) {
+ uint32_t dspFrames = 0;
+ if (mOutput != 0) {
+ uint32_t halFrames;
+ AudioSystem::getRenderPosition(mOutput, &halFrames, &dspFrames);
+ }
+ *position = dspFrames;
+ } else {
+ // IAudioTrack::stop() isn't synchronous; we don't know when presentation completes
+ *position = (mState == STATE_STOPPED || mState == STATE_FLUSHED) ? 0 :
+ mProxy->getPosition();
+ }
return NO_ERROR;
}
-status_t AudioTrack::getPosition(uint32_t *position)
+status_t AudioTrack::getBufferPosition(size_t *position)
{
+ if (mSharedBuffer == 0 || mIsTimed) {
+ return INVALID_OPERATION;
+ }
if (position == NULL) {
return BAD_VALUE;
}
- AutoMutex lock(mLock);
- *position = mFlushed ? 0 : mCblk->server;
+ AutoMutex lock(mLock);
+ *position = mStaticProxy->getBufferPosition();
return NO_ERROR;
}
status_t AudioTrack::reload()
{
- if (mStatus != NO_ERROR) {
- return mStatus;
- }
- ALOG_ASSERT(mProxy != NULL);
-
- if (mSharedBuffer == 0 || mIsTimed) {
+ if (mSharedBuffer == 0 || mIsTimed || isOffloaded()) {
return INVALID_OPERATION;
}
AutoMutex lock(mLock);
-
- if (!stopped_l()) {
+ // See setPosition() regarding setting parameters such as loop points or position while active
+ if (mState == STATE_ACTIVE) {
return INVALID_OPERATION;
}
-
- flush_l();
-
- (void) mProxy->stepUser(mFrameCount);
-
+ mNewPosition = mUpdatePeriod;
+ mLoopPeriod = 0;
+ // FIXME The new code cannot reload while keeping a loop specified.
+ // Need to check how the old code handled this, and whether it's a significant change.
+ mStaticProxy->setLoop(0, mFrameCount, 0);
return NO_ERROR;
}
audio_io_handle_t AudioTrack::getOutput()
{
AutoMutex lock(mLock);
- return getOutput_l();
+ return mOutput;
}
// must be called with mLock held
audio_io_handle_t AudioTrack::getOutput_l()
{
- return AudioSystem::getOutput(mStreamType,
- mSampleRate, mFormat, mChannelMask, mFlags);
+ if (mOutput) {
+ return mOutput;
+ } else {
+ return AudioSystem::getOutput(mStreamType,
+ mSampleRate, mFormat, mChannelMask, mFlags);
+ }
}
status_t AudioTrack::attachAuxEffect(int effectId)
{
- ALOGV("attachAuxEffect(%d)", effectId);
+ AutoMutex lock(mLock);
status_t status = mAudioTrack->attachAuxEffect(effectId);
if (status == NO_ERROR) {
mAuxEffectId = effectId;
@@ -737,7 +808,8 @@ status_t AudioTrack::createTrack_l(
size_t frameCount,
audio_output_flags_t flags,
const sp<IMemory>& sharedBuffer,
- audio_io_handle_t output)
+ audio_io_handle_t output,
+ size_t epoch)
{
status_t status;
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
@@ -746,8 +818,26 @@ status_t AudioTrack::createTrack_l(
return NO_INIT;
}
+ // Not all of these values are needed under all conditions, but it is easier to get them all
+
uint32_t afLatency;
- if (AudioSystem::getLatency(output, streamType, &afLatency) != NO_ERROR) {
+ status = AudioSystem::getLatency(output, streamType, &afLatency);
+ if (status != NO_ERROR) {
+ ALOGE("getLatency(%d) failed status %d", output, status);
+ return NO_INIT;
+ }
+
+ size_t afFrameCount;
+ status = AudioSystem::getFrameCount(output, streamType, &afFrameCount);
+ if (status != NO_ERROR) {
+ ALOGE("getFrameCount(output=%d, streamType=%d) status %d", output, streamType, status);
+ return NO_INIT;
+ }
+
+ uint32_t afSampleRate;
+ status = AudioSystem::getSamplingRate(output, streamType, &afSampleRate);
+ if (status != NO_ERROR) {
+ ALOGE("getSamplingRate(output=%d, streamType=%d) status %d", output, streamType, status);
return NO_INIT;
}
@@ -766,6 +856,14 @@ status_t AudioTrack::createTrack_l(
}
ALOGV("createTrack_l() output %d afLatency %d", output, afLatency);
+ // The client's AudioTrack buffer is divided into n parts for purpose of wakeup by server, where
+ // n = 1 fast track; nBuffering is ignored
+ // n = 2 normal track, no sample rate conversion
+ // n = 3 normal track, with sample rate conversion
+ // (pessimistic; some non-1:1 conversion ratios don't actually need triple-buffering)
+ // n > 3 very high latency or very small notification interval; nBuffering is ignored
+ const uint32_t nBuffering = (sampleRate == afSampleRate) ? 2 : 3;
+
mNotificationFramesAct = mNotificationFramesReq;
if (!audio_is_linear_pcm(format)) {
@@ -774,13 +872,11 @@ status_t AudioTrack::createTrack_l(
// Same comment as below about ignoring frameCount parameter for set()
frameCount = sharedBuffer->size();
} else if (frameCount == 0) {
- size_t afFrameCount;
- if (AudioSystem::getFrameCount(output, streamType, &afFrameCount) != NO_ERROR) {
- return NO_INIT;
- }
frameCount = afFrameCount;
}
-
+ if (mNotificationFramesAct != frameCount) {
+ mNotificationFramesAct = frameCount;
+ }
} else if (sharedBuffer != 0) {
// Ensure that buffer alignment matches channel count
@@ -805,18 +901,14 @@ status_t AudioTrack::createTrack_l(
} else if (!(flags & AUDIO_OUTPUT_FLAG_FAST)) {
// FIXME move these calculations and associated checks to server
- uint32_t afSampleRate;
- if (AudioSystem::getSamplingRate(output, streamType, &afSampleRate) != NO_ERROR) {
- return NO_INIT;
- }
- size_t afFrameCount;
- if (AudioSystem::getFrameCount(output, streamType, &afFrameCount) != NO_ERROR) {
- return NO_INIT;
- }
// Ensure that buffer depth covers at least audio hardware latency
uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate);
- if (minBufCount < 2) minBufCount = 2;
+ ALOGV("afFrameCount=%d, minBufCount=%d, afSampleRate=%u, afLatency=%d",
+ afFrameCount, minBufCount, afSampleRate, afLatency);
+ if (minBufCount <= nBuffering) {
+ minBufCount = nBuffering;
+ }
size_t minFrameCount = (afFrameCount*sampleRate*minBufCount)/afSampleRate;
ALOGV("minFrameCount: %u, afFrameCount=%d, minBufCount=%d, sampleRate=%u, afSampleRate=%u"
@@ -825,21 +917,16 @@ status_t AudioTrack::createTrack_l(
if (frameCount == 0) {
frameCount = minFrameCount;
- }
- if (mNotificationFramesAct == 0) {
- mNotificationFramesAct = frameCount/2;
- }
- // Make sure that application is notified with sufficient margin
- // before underrun
- if (mNotificationFramesAct > frameCount/2) {
- mNotificationFramesAct = frameCount/2;
- }
- if (frameCount < minFrameCount) {
+ } else if (frameCount < minFrameCount) {
// not ALOGW because it happens all the time when playing key clicks over A2DP
ALOGV("Minimum buffer size corrected from %d to %d",
frameCount, minFrameCount);
frameCount = minFrameCount;
}
+ // Make sure that application is notified with sufficient margin before underrun
+ if (mNotificationFramesAct == 0 || mNotificationFramesAct > frameCount/nBuffering) {
+ mNotificationFramesAct = frameCount/nBuffering;
+ }
} else {
// For fast tracks, the frame count calculations and checks are done by server
@@ -858,6 +945,10 @@ status_t AudioTrack::createTrack_l(
}
}
+ if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
+ trackFlags |= IAudioFlinger::TRACK_OFFLOAD;
+ }
+
sp<IAudioTrack> track = audioFlinger->createTrack(streamType,
sampleRate,
// AudioFlinger only sees 16-bit PCM
@@ -870,6 +961,7 @@ status_t AudioTrack::createTrack_l(
output,
tid,
&mSessionId,
+ mName,
&status);
if (track == 0) {
@@ -881,6 +973,11 @@ status_t AudioTrack::createTrack_l(
ALOGE("Could not get control block");
return NO_INIT;
}
+ // invariant that mAudioTrack != 0 is true only after set() returns successfully
+ if (mAudioTrack != 0) {
+ mAudioTrack->asBinder()->unlinkToDeath(mDeathNotifier, this);
+ mDeathNotifier.clear();
+ }
mAudioTrack = track;
mCblkMemory = iMem;
audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMem->pointer());
@@ -898,26 +995,49 @@ status_t AudioTrack::createTrack_l(
if (trackFlags & IAudioFlinger::TRACK_FAST) {
ALOGV("AUDIO_OUTPUT_FLAG_FAST successful; frameCount %u", frameCount);
mAwaitBoost = true;
+ if (sharedBuffer == 0) {
+ // double-buffering is not required for fast tracks, due to tighter scheduling
+ if (mNotificationFramesAct == 0 || mNotificationFramesAct > frameCount) {
+ mNotificationFramesAct = frameCount;
+ }
+ }
} else {
ALOGV("AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %u", frameCount);
// once denied, do not request again if IAudioTrack is re-created
flags = (audio_output_flags_t) (flags & ~AUDIO_OUTPUT_FLAG_FAST);
mFlags = flags;
+ if (sharedBuffer == 0) {
+ if (mNotificationFramesAct == 0 || mNotificationFramesAct > frameCount/nBuffering) {
+ mNotificationFramesAct = frameCount/nBuffering;
+ }
+ }
}
- if (sharedBuffer == 0) {
- mNotificationFramesAct = frameCount/2;
+ }
+ if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
+ if (trackFlags & IAudioFlinger::TRACK_OFFLOAD) {
+ ALOGV("AUDIO_OUTPUT_FLAG_OFFLOAD successful");
+ } else {
+ ALOGW("AUDIO_OUTPUT_FLAG_OFFLOAD denied by server");
+ flags = (audio_output_flags_t) (flags & ~AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD);
+ mFlags = flags;
+ return NO_INIT;
}
}
+
+ mRefreshRemaining = true;
+
+ // Starting address of buffers in shared memory. If there is a shared buffer, buffers
+ // is the value of pointer() for the shared buffer, otherwise buffers points
+ // immediately after the control block. This address is for the mapping within client
+ // address space. AudioFlinger::TrackBase::mBuffer is for the server address space.
+ void* buffers;
if (sharedBuffer == 0) {
- mBuffers = (char*)cblk + sizeof(audio_track_cblk_t);
+ buffers = (char*)cblk + sizeof(audio_track_cblk_t);
} else {
- mBuffers = sharedBuffer->pointer();
+ buffers = sharedBuffer->pointer();
}
mAudioTrack->attachAuxEffect(mAuxEffectId);
- cblk->bufferTimeoutMs = MAX_STARTUP_TIMEOUT_MS;
- cblk->waitTimeMs = 0;
- mRemainingFrames = mNotificationFramesAct;
// FIXME don't believe this lie
mLatency = afLatency + (1000*frameCount) / sampleRate;
mFrameCount = frameCount;
@@ -928,147 +1048,154 @@ status_t AudioTrack::createTrack_l(
}
// update proxy
- delete mProxy;
- mProxy = new AudioTrackClientProxy(cblk, mBuffers, frameCount, mFrameSizeAF);
+ if (sharedBuffer == 0) {
+ mStaticProxy.clear();
+ mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSizeAF);
+ } else {
+ mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSizeAF);
+ mProxy = mStaticProxy;
+ }
mProxy->setVolumeLR((uint32_t(uint16_t(mVolume[RIGHT] * 0x1000)) << 16) |
uint16_t(mVolume[LEFT] * 0x1000));
mProxy->setSendLevel(mSendLevel);
mProxy->setSampleRate(mSampleRate);
- if (sharedBuffer != 0) {
- // Force buffer full condition as data is already present in shared memory
- mProxy->stepUser(frameCount);
- }
+ mProxy->setEpoch(epoch);
+ mProxy->setMinimum(mNotificationFramesAct);
+
+ mDeathNotifier = new DeathNotifier(this);
+ mAudioTrack->asBinder()->linkToDeath(mDeathNotifier, this);
return NO_ERROR;
}
status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, int32_t waitCount)
{
- ALOG_ASSERT(mStatus == NO_ERROR && mProxy != NULL);
+ if (audioBuffer == NULL) {
+ return BAD_VALUE;
+ }
+ if (mTransfer != TRANSFER_OBTAIN) {
+ audioBuffer->frameCount = 0;
+ audioBuffer->size = 0;
+ audioBuffer->raw = NULL;
+ return INVALID_OPERATION;
+ }
- AutoMutex lock(mLock);
- bool active;
- status_t result = NO_ERROR;
- audio_track_cblk_t* cblk = mCblk;
- uint32_t framesReq = audioBuffer->frameCount;
- uint32_t waitTimeMs = (waitCount < 0) ? cblk->bufferTimeoutMs : WAIT_PERIOD_MS;
+ const struct timespec *requested;
+ if (waitCount == -1) {
+ requested = &ClientProxy::kForever;
+ } else if (waitCount == 0) {
+ requested = &ClientProxy::kNonBlocking;
+ } else if (waitCount > 0) {
+ long long ms = WAIT_PERIOD_MS * (long long) waitCount;
+ struct timespec timeout;
+ timeout.tv_sec = ms / 1000;
+ timeout.tv_nsec = (int) (ms % 1000) * 1000000;
+ requested = &timeout;
+ } else {
+ ALOGE("%s invalid waitCount %d", __func__, waitCount);
+ requested = NULL;
+ }
+ return obtainBuffer(audioBuffer, requested);
+}
+
+status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,
+ struct timespec *elapsed, size_t *nonContig)
+{
+ // previous and new IAudioTrack sequence numbers are used to detect track re-creation
+ uint32_t oldSequence = 0;
+ uint32_t newSequence;
- audioBuffer->frameCount = 0;
- audioBuffer->size = 0;
+ Proxy::Buffer buffer;
+ status_t status = NO_ERROR;
- size_t framesAvail = mProxy->framesAvailable();
+ static const int32_t kMaxTries = 5;
+ int32_t tryCounter = kMaxTries;
- cblk->lock.lock();
- if (cblk->flags & CBLK_INVALID) {
- goto create_new_track;
- }
- cblk->lock.unlock();
+ do {
+ // obtainBuffer() is called with mutex unlocked, so keep extra references to these fields to
+ // keep them from going away if another thread re-creates the track during obtainBuffer()
+ sp<AudioTrackClientProxy> proxy;
+ sp<IMemory> iMem;
- if (framesAvail == 0) {
- cblk->lock.lock();
- goto start_loop_here;
- while (framesAvail == 0) {
- active = mActive;
- if (CC_UNLIKELY(!active)) {
- ALOGV("Not active and NO_MORE_BUFFERS");
- cblk->lock.unlock();
- return NO_MORE_BUFFERS;
- }
- if (CC_UNLIKELY(!waitCount)) {
- cblk->lock.unlock();
- return WOULD_BLOCK;
- }
- if (!(cblk->flags & CBLK_INVALID)) {
- mLock.unlock();
- // this condition is in shared memory, so if IAudioTrack and control block
- // are replaced due to mediaserver death or IAudioTrack invalidation then
- // cv won't be signalled, but fortunately the timeout will limit the wait
- result = cblk->cv.waitRelative(cblk->lock, milliseconds(waitTimeMs));
- cblk->lock.unlock();
- mLock.lock();
- if (!mActive) {
- return status_t(STOPPED);
+ { // start of lock scope
+ AutoMutex lock(mLock);
+
+ newSequence = mSequence;
+ // did previous obtainBuffer() fail due to media server death or voluntary invalidation?
+ if (status == DEAD_OBJECT) {
+ // re-create track, unless someone else has already done so
+ if (newSequence == oldSequence) {
+ status = restoreTrack_l("obtainBuffer");
+ if (status != NO_ERROR) {
+ buffer.mFrameCount = 0;
+ buffer.mRaw = NULL;
+ buffer.mNonContig = 0;
+ break;
+ }
}
- // IAudioTrack may have been re-created while mLock was unlocked
- cblk = mCblk;
- cblk->lock.lock();
}
+ oldSequence = newSequence;
+
+ // Keep the extra references
+ proxy = mProxy;
+ iMem = mCblkMemory;
- if (cblk->flags & CBLK_INVALID) {
- goto create_new_track;
+ if (mState == STATE_STOPPING) {
+ status = -EINTR;
+ buffer.mFrameCount = 0;
+ buffer.mRaw = NULL;
+ buffer.mNonContig = 0;
+ break;
}
- if (CC_UNLIKELY(result != NO_ERROR)) {
- cblk->waitTimeMs += waitTimeMs;
- if (cblk->waitTimeMs >= cblk->bufferTimeoutMs) {
- // timing out when a loop has been set and we have already written upto loop end
- // is a normal condition: no need to wake AudioFlinger up.
- if (cblk->user < cblk->loopEnd) {
- ALOGW("obtainBuffer timed out (is the CPU pegged?) %p name=%#x user=%08x, "
- "server=%08x", this, cblk->mName, cblk->user, cblk->server);
- //unlock cblk mutex before calling mAudioTrack->start() (see issue #1617140)
- cblk->lock.unlock();
- result = mAudioTrack->start();
- cblk->lock.lock();
- if (result == DEAD_OBJECT) {
- android_atomic_or(CBLK_INVALID, &cblk->flags);
-create_new_track:
- audio_track_cblk_t* temp = cblk;
- result = restoreTrack_l(temp, false /*fromStart*/);
- cblk = temp;
- }
- if (result != NO_ERROR) {
- ALOGW("obtainBuffer create Track error %d", result);
- cblk->lock.unlock();
- return result;
- }
- }
- cblk->waitTimeMs = 0;
- }
- if (--waitCount == 0) {
- cblk->lock.unlock();
- return TIMED_OUT;
- }
+ // Non-blocking if track is stopped or paused
+ if (mState != STATE_ACTIVE) {
+ requested = &ClientProxy::kNonBlocking;
}
- // read the server count again
- start_loop_here:
- framesAvail = mProxy->framesAvailable_l();
- }
- cblk->lock.unlock();
- }
- cblk->waitTimeMs = 0;
+ } // end of lock scope
- if (framesReq > framesAvail) {
- framesReq = framesAvail;
- }
+ buffer.mFrameCount = audioBuffer->frameCount;
+ // FIXME starts the requested timeout and elapsed over from scratch
+ status = proxy->obtainBuffer(&buffer, requested, elapsed);
- uint32_t u = cblk->user;
- uint32_t bufferEnd = cblk->userBase + mFrameCount;
+ } while ((status == DEAD_OBJECT) && (tryCounter-- > 0));
- if (framesReq > bufferEnd - u) {
- framesReq = bufferEnd - u;
+ audioBuffer->frameCount = buffer.mFrameCount;
+ audioBuffer->size = buffer.mFrameCount * mFrameSizeAF;
+ audioBuffer->raw = buffer.mRaw;
+ if (nonContig != NULL) {
+ *nonContig = buffer.mNonContig;
}
-
- audioBuffer->frameCount = framesReq;
- audioBuffer->size = framesReq * mFrameSizeAF;
- audioBuffer->raw = mProxy->buffer(u);
- active = mActive;
- return active ? status_t(NO_ERROR) : status_t(STOPPED);
+ return status;
}
void AudioTrack::releaseBuffer(Buffer* audioBuffer)
{
- ALOG_ASSERT(mStatus == NO_ERROR && mProxy != NULL);
+ if (mTransfer == TRANSFER_SHARED) {
+ return;
+ }
+
+ size_t stepCount = audioBuffer->size / mFrameSizeAF;
+ if (stepCount == 0) {
+ return;
+ }
+
+ Proxy::Buffer buffer;
+ buffer.mFrameCount = stepCount;
+ buffer.mRaw = audioBuffer->raw;
AutoMutex lock(mLock);
- audio_track_cblk_t* cblk = mCblk;
- (void) mProxy->stepUser(audioBuffer->frameCount);
- if (audioBuffer->frameCount > 0) {
- // restart track if it was disabled by audioflinger due to previous underrun
- if (mActive && (cblk->flags & CBLK_DISABLED)) {
- android_atomic_and(~CBLK_DISABLED, &cblk->flags);
- ALOGW("releaseBuffer() track %p name=%#x disabled, restarting", this, cblk->mName);
+ mInUnderrun = false;
+ mProxy->releaseBuffer(&buffer);
+
+ // restart track if it was disabled by audioflinger due to previous underrun
+ if (mState == STATE_ACTIVE) {
+ audio_track_cblk_t* cblk = mCblk;
+ if (android_atomic_and(~CBLK_DISABLED, &cblk->mFlags) & CBLK_DISABLED) {
+ ALOGW("releaseBuffer() track %p name=%s disabled due to previous underrun, restarting",
+ this, mName.string());
+ // FIXME ignoring status
mAudioTrack->start();
}
}
@@ -1078,68 +1205,46 @@ void AudioTrack::releaseBuffer(Buffer* audioBuffer)
ssize_t AudioTrack::write(const void* buffer, size_t userSize)
{
-
- if (mSharedBuffer != 0 || mIsTimed) {
+ if (mTransfer != TRANSFER_SYNC || mIsTimed) {
return INVALID_OPERATION;
}
- if (ssize_t(userSize) < 0) {
+ if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) {
// Sanity-check: user is most-likely passing an error code, and it would
// make the return value ambiguous (actualSize vs error).
- ALOGE("AudioTrack::write(buffer=%p, size=%u (%d)",
- buffer, userSize, userSize);
+ ALOGE("AudioTrack::write(buffer=%p, size=%u (%d)", buffer, userSize, userSize);
return BAD_VALUE;
}
- ALOGV("write %p: %d bytes, mActive=%d", this, userSize, mActive);
-
- if (userSize == 0) {
- return 0;
- }
-
- // acquire a strong reference on the IMemory and IAudioTrack so that they cannot be destroyed
- // while we are accessing the cblk
- mLock.lock();
- sp<IAudioTrack> audioTrack = mAudioTrack;
- sp<IMemory> iMem = mCblkMemory;
- mLock.unlock();
-
- // since mLock is unlocked the IAudioTrack and shared memory may be re-created,
- // so all cblk references might still refer to old shared memory, but that should be benign
-
- ssize_t written = 0;
- const int8_t *src = (const int8_t *)buffer;
+ size_t written = 0;
Buffer audioBuffer;
- size_t frameSz = frameSize();
- do {
- audioBuffer.frameCount = userSize/frameSz;
+ while (userSize >= mFrameSize) {
+ audioBuffer.frameCount = userSize / mFrameSize;
- status_t err = obtainBuffer(&audioBuffer, -1);
+ status_t err = obtainBuffer(&audioBuffer, &ClientProxy::kForever);
if (err < 0) {
- // out of buffers, return #bytes written
- if (err == status_t(NO_MORE_BUFFERS)) {
+ if (written > 0) {
break;
}
return ssize_t(err);
}
size_t toWrite;
-
if (mFormat == AUDIO_FORMAT_PCM_8_BIT && !(mFlags & AUDIO_OUTPUT_FLAG_DIRECT)) {
// Divide capacity by 2 to take expansion into account
- toWrite = audioBuffer.size>>1;
- memcpy_to_i16_from_u8(audioBuffer.i16, (const uint8_t *) src, toWrite);
+ toWrite = audioBuffer.size >> 1;
+ memcpy_to_i16_from_u8(audioBuffer.i16, (const uint8_t *) buffer, toWrite);
} else {
toWrite = audioBuffer.size;
- memcpy(audioBuffer.i8, src, toWrite);
+ memcpy(audioBuffer.i8, buffer, toWrite);
}
- src += toWrite;
+ buffer = ((const char *) buffer) + toWrite;
userSize -= toWrite;
written += toWrite;
releaseBuffer(&audioBuffer);
- } while (userSize >= frameSz);
+ }
return written;
}
@@ -1155,32 +1260,30 @@ status_t TimedAudioTrack::allocateTimedBuffer(size_t size, sp<IMemory>* buffer)
AutoMutex lock(mLock);
status_t result = UNKNOWN_ERROR;
+#if 1
// acquire a strong reference on the IMemory and IAudioTrack so that they cannot be destroyed
// while we are accessing the cblk
sp<IAudioTrack> audioTrack = mAudioTrack;
sp<IMemory> iMem = mCblkMemory;
+#endif
// If the track is not invalid already, try to allocate a buffer. alloc
// fails indicating that the server is dead, flag the track as invalid so
// we can attempt to restore in just a bit.
audio_track_cblk_t* cblk = mCblk;
- if (!(cblk->flags & CBLK_INVALID)) {
+ if (!(cblk->mFlags & CBLK_INVALID)) {
result = mAudioTrack->allocateTimedBuffer(size, buffer);
if (result == DEAD_OBJECT) {
- android_atomic_or(CBLK_INVALID, &cblk->flags);
+ android_atomic_or(CBLK_INVALID, &cblk->mFlags);
}
}
// If the track is invalid at this point, attempt to restore it. and try the
// allocation one more time.
- if (cblk->flags & CBLK_INVALID) {
- cblk->lock.lock();
- audio_track_cblk_t* temp = cblk;
- result = restoreTrack_l(temp, false /*fromStart*/);
- cblk = temp;
- cblk->lock.unlock();
-
- if (result == OK) {
+ if (cblk->mFlags & CBLK_INVALID) {
+ result = restoreTrack_l("allocateTimedBuffer");
+
+ if (result == NO_ERROR) {
result = mAudioTrack->allocateTimedBuffer(size, buffer);
}
}
@@ -1197,9 +1300,10 @@ status_t TimedAudioTrack::queueTimedBuffer(const sp<IMemory>& buffer,
audio_track_cblk_t* cblk = mCblk;
// restart track if it was disabled by audioflinger due to previous underrun
if (buffer->size() != 0 && status == NO_ERROR &&
- mActive && (cblk->flags & CBLK_DISABLED)) {
- android_atomic_and(~CBLK_DISABLED, &cblk->flags);
+ (mState == STATE_ACTIVE) && (cblk->mFlags & CBLK_DISABLED)) {
+ android_atomic_and(~CBLK_DISABLED, &cblk->mFlags);
ALOGW("queueTimedBuffer() track %p disabled, restarting", this);
+ // FIXME ignoring status
mAudioTrack->start();
}
}
@@ -1214,11 +1318,12 @@ status_t TimedAudioTrack::setMediaTimeTransform(const LinearTransform& xform,
// -------------------------------------------------------------------------
-bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)
+nsecs_t AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)
{
- Buffer audioBuffer;
- uint32_t frames;
- size_t writtenSize;
+ // Currently the AudioTrack thread is not created if there are no callbacks.
+ // Would it ever make sense to run the thread, even without callbacks?
+ // If so, then replace this by checks at each use for mCbf != NULL.
+ LOG_ALWAYS_FATAL_IF(mCblk == NULL);
mLock.lock();
if (mAwaitBoost) {
@@ -1238,88 +1343,228 @@ bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)
if (tryCounter < 0) {
ALOGE("did not receive expected priority boost on time");
}
- return true;
+ // Run again immediately
+ return 0;
}
- // acquire a strong reference on the IMemory and IAudioTrack so that they cannot be destroyed
- // while we are accessing the cblk
- sp<IAudioTrack> audioTrack = mAudioTrack;
- sp<IMemory> iMem = mCblkMemory;
- audio_track_cblk_t* cblk = mCblk;
- bool active = mActive;
+
+ // Can only reference mCblk while locked
+ int32_t flags = android_atomic_and(
+ ~(CBLK_UNDERRUN | CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL | CBLK_BUFFER_END), &mCblk->mFlags);
+
+ // Check for track invalidation
+ if (flags & CBLK_INVALID) {
+ // for offloaded tracks restoreTrack_l() will just update the sequence and clear
+ // AudioSystem cache. We should not exit here but after calling the callback so
+ // that the upper layers can recreate the track
+ if (!isOffloaded() || (mSequence == mObservedSequence)) {
+ status_t status = restoreTrack_l("processAudioBuffer");
+ mLock.unlock();
+ // Run again immediately, but with a new IAudioTrack
+ return 0;
+ }
+ }
+
+ bool waitStreamEnd = mState == STATE_STOPPING;
+ bool active = mState == STATE_ACTIVE;
+
+ // Manage underrun callback, must be done under lock to avoid race with releaseBuffer()
+ bool newUnderrun = false;
+ if (flags & CBLK_UNDERRUN) {
+#if 0
+ // Currently in shared buffer mode, when the server reaches the end of buffer,
+ // the track stays active in continuous underrun state. It's up to the application
+ // to pause or stop the track, or set the position to a new offset within buffer.
+ // This was some experimental code to auto-pause on underrun. Keeping it here
+ // in "if 0" so we can re-visit this if we add a real sequencer for shared memory content.
+ if (mTransfer == TRANSFER_SHARED) {
+ mState = STATE_PAUSED;
+ active = false;
+ }
+#endif
+ if (!mInUnderrun) {
+ mInUnderrun = true;
+ newUnderrun = true;
+ }
+ }
+
+ // Get current position of server
+ size_t position = mProxy->getPosition();
+
+ // Manage marker callback
+ bool markerReached = false;
+ size_t markerPosition = mMarkerPosition;
+ // FIXME fails for wraparound, need 64 bits
+ if (!mMarkerReached && (markerPosition > 0) && (position >= markerPosition)) {
+ mMarkerReached = markerReached = true;
+ }
+
+ // Determine number of new position callback(s) that will be needed, while locked
+ size_t newPosCount = 0;
+ size_t newPosition = mNewPosition;
+ size_t updatePeriod = mUpdatePeriod;
+ // FIXME fails for wraparound, need 64 bits
+ if (updatePeriod > 0 && position >= newPosition) {
+ newPosCount = ((position - newPosition) / updatePeriod) + 1;
+ mNewPosition += updatePeriod * newPosCount;
+ }
+
+ // Cache other fields that will be needed soon
+ uint32_t loopPeriod = mLoopPeriod;
+ uint32_t sampleRate = mSampleRate;
+ size_t notificationFrames = mNotificationFramesAct;
+ if (mRefreshRemaining) {
+ mRefreshRemaining = false;
+ mRemainingFrames = notificationFrames;
+ mRetryOnPartialBuffer = false;
+ }
+ size_t misalignment = mProxy->getMisalignment();
+ uint32_t sequence = mSequence;
+
+ // These fields don't need to be cached, because they are assigned only by set():
+ // mTransfer, mCbf, mUserData, mFormat, mFrameSize, mFrameSizeAF, mFlags
+ // mFlags is also assigned by createTrack_l(), but not the bit we care about.
+
mLock.unlock();
- // since mLock is unlocked the IAudioTrack and shared memory may be re-created,
- // so all cblk references might still refer to old shared memory, but that should be benign
+ if (waitStreamEnd) {
+ AutoMutex lock(mLock);
- // Manage underrun callback
- if (active && (mProxy->framesAvailable() == mFrameCount)) {
- ALOGV("Underrun user: %x, server: %x, flags %04x", cblk->user, cblk->server, cblk->flags);
- if (!(android_atomic_or(CBLK_UNDERRUN, &cblk->flags) & CBLK_UNDERRUN)) {
- mCbf(EVENT_UNDERRUN, mUserData, 0);
- if (cblk->server == mFrameCount) {
- mCbf(EVENT_BUFFER_END, mUserData, 0);
- }
- if (mSharedBuffer != 0) {
- return false;
+ sp<AudioTrackClientProxy> proxy = mProxy;
+ sp<IMemory> iMem = mCblkMemory;
+
+ struct timespec timeout;
+ timeout.tv_sec = WAIT_STREAM_END_TIMEOUT_SEC;
+ timeout.tv_nsec = 0;
+
+ mLock.unlock();
+ status_t status = mProxy->waitStreamEndDone(&timeout);
+ mLock.lock();
+ switch (status) {
+ case NO_ERROR:
+ case DEAD_OBJECT:
+ case TIMED_OUT:
+ mLock.unlock();
+ mCbf(EVENT_STREAM_END, mUserData, NULL);
+ mLock.lock();
+ if (mState == STATE_STOPPING) {
+ mState = STATE_STOPPED;
+ if (status != DEAD_OBJECT) {
+ return NS_INACTIVE;
+ }
}
+ return 0;
+ default:
+ return 0;
}
}
- // Manage loop end callback
- while (mLoopCount > cblk->loopCount) {
- int loopCount = -1;
- mLoopCount--;
- if (mLoopCount >= 0) loopCount = mLoopCount;
-
- mCbf(EVENT_LOOP_END, mUserData, (void *)&loopCount);
+ // perform callbacks while unlocked
+ if (newUnderrun) {
+ mCbf(EVENT_UNDERRUN, mUserData, NULL);
+ }
+ // FIXME we will miss loops if loop cycle was signaled several times since last call
+ // to processAudioBuffer()
+ if (flags & (CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL)) {
+ mCbf(EVENT_LOOP_END, mUserData, NULL);
+ }
+ if (flags & CBLK_BUFFER_END) {
+ mCbf(EVENT_BUFFER_END, mUserData, NULL);
+ }
+ if (markerReached) {
+ mCbf(EVENT_MARKER, mUserData, &markerPosition);
+ }
+ while (newPosCount > 0) {
+ size_t temp = newPosition;
+ mCbf(EVENT_NEW_POS, mUserData, &temp);
+ newPosition += updatePeriod;
+ newPosCount--;
}
- // Manage marker callback
- if (!mMarkerReached && (mMarkerPosition > 0)) {
- if (cblk->server >= mMarkerPosition) {
- mCbf(EVENT_MARKER, mUserData, (void *)&mMarkerPosition);
- mMarkerReached = true;
+ if (mObservedSequence != sequence) {
+ mObservedSequence = sequence;
+ mCbf(EVENT_NEW_IAUDIOTRACK, mUserData, NULL);
+ // for offloaded tracks, just wait for the upper layers to recreate the track
+ if (isOffloaded()) {
+ return NS_INACTIVE;
}
}
- // Manage new position callback
- if (mUpdatePeriod > 0) {
- while (cblk->server >= mNewPosition) {
- mCbf(EVENT_NEW_POS, mUserData, (void *)&mNewPosition);
- mNewPosition += mUpdatePeriod;
- }
+ // if inactive, then don't run me again until re-started
+ if (!active) {
+ return NS_INACTIVE;
}
- // If Shared buffer is used, no data is requested from client.
- if (mSharedBuffer != 0) {
- frames = 0;
- } else {
- frames = mRemainingFrames;
+ // Compute the estimated time until the next timed event (position, markers, loops)
+ // FIXME only for non-compressed audio
+ uint32_t minFrames = ~0;
+ if (!markerReached && position < markerPosition) {
+ minFrames = markerPosition - position;
+ }
+ if (loopPeriod > 0 && loopPeriod < minFrames) {
+ minFrames = loopPeriod;
+ }
+ if (updatePeriod > 0 && updatePeriod < minFrames) {
+ minFrames = updatePeriod;
}
- // See description of waitCount parameter at declaration of obtainBuffer().
- // The logic below prevents us from being stuck below at obtainBuffer()
- // not being able to handle timed events (position, markers, loops).
- int32_t waitCount = -1;
- if (mUpdatePeriod || (!mMarkerReached && mMarkerPosition) || mLoopCount) {
- waitCount = 1;
+ // If > 0, poll periodically to recover from a stuck server. A good value is 2.
+ static const uint32_t kPoll = 0;
+ if (kPoll > 0 && mTransfer == TRANSFER_CALLBACK && kPoll * notificationFrames < minFrames) {
+ minFrames = kPoll * notificationFrames;
}
- do {
+ // Convert frame units to time units
+ nsecs_t ns = NS_WHENEVER;
+ if (minFrames != (uint32_t) ~0) {
+ // This "fudge factor" avoids soaking CPU, and compensates for late progress by server
+ static const nsecs_t kFudgeNs = 10000000LL; // 10 ms
+ ns = ((minFrames * 1000000000LL) / sampleRate) + kFudgeNs;
+ }
+
+ // If not supplying data by EVENT_MORE_DATA, then we're done
+ if (mTransfer != TRANSFER_CALLBACK) {
+ return ns;
+ }
+
+ struct timespec timeout;
+ const struct timespec *requested = &ClientProxy::kForever;
+ if (ns != NS_WHENEVER) {
+ timeout.tv_sec = ns / 1000000000LL;
+ timeout.tv_nsec = ns % 1000000000LL;
+ ALOGV("timeout %ld.%03d", timeout.tv_sec, (int) timeout.tv_nsec / 1000000);
+ requested = &timeout;
+ }
- audioBuffer.frameCount = frames;
+ while (mRemainingFrames > 0) {
- status_t err = obtainBuffer(&audioBuffer, waitCount);
- if (err < NO_ERROR) {
- if (err != TIMED_OUT) {
- ALOGE_IF(err != status_t(NO_MORE_BUFFERS),
- "Error obtaining an audio buffer, giving up.");
- return false;
+ Buffer audioBuffer;
+ audioBuffer.frameCount = mRemainingFrames;
+ size_t nonContig;
+ status_t err = obtainBuffer(&audioBuffer, requested, NULL, &nonContig);
+ LOG_ALWAYS_FATAL_IF((err != NO_ERROR) != (audioBuffer.frameCount == 0),
+ "obtainBuffer() err=%d frameCount=%u", err, audioBuffer.frameCount);
+ requested = &ClientProxy::kNonBlocking;
+ size_t avail = audioBuffer.frameCount + nonContig;
+ ALOGV("obtainBuffer(%u) returned %u = %u + %u err %d",
+ mRemainingFrames, avail, audioBuffer.frameCount, nonContig, err);
+ if (err != NO_ERROR) {
+ if (err == TIMED_OUT || err == WOULD_BLOCK || err == -EINTR ||
+ (isOffloaded() && (err == DEAD_OBJECT))) {
+ return 0;
}
- break;
+ ALOGE("Error %d obtaining an audio buffer, giving up.", err);
+ return NS_NEVER;
}
- if (err == status_t(STOPPED)) {
- return false;
+
+ if (mRetryOnPartialBuffer && !isOffloaded()) {
+ mRetryOnPartialBuffer = false;
+ if (avail < mRemainingFrames) {
+ int64_t myns = ((mRemainingFrames - avail) * 1100000000LL) / sampleRate;
+ if (ns < 0 || myns < ns) {
+ ns = myns;
+ }
+ return ns;
+ }
}
// Divide buffer size by 2 to take into account the expansion
@@ -1331,139 +1576,175 @@ bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)
size_t reqSize = audioBuffer.size;
mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);
- writtenSize = audioBuffer.size;
+ size_t writtenSize = audioBuffer.size;
+ size_t writtenFrames = writtenSize / mFrameSize;
// Sanity check on returned size
- if (ssize_t(writtenSize) <= 0) {
+ if (ssize_t(writtenSize) < 0 || writtenSize > reqSize) {
+ ALOGE("EVENT_MORE_DATA requested %u bytes but callback returned %d bytes",
+ reqSize, (int) writtenSize);
+ return NS_NEVER;
+ }
+
+ if (writtenSize == 0) {
// The callback is done filling buffers
// Keep this thread going to handle timed events and
// still try to get more data in intervals of WAIT_PERIOD_MS
// but don't just loop and block the CPU, so wait
- usleep(WAIT_PERIOD_MS*1000);
- break;
- }
-
- if (writtenSize > reqSize) {
- writtenSize = reqSize;
+ return WAIT_PERIOD_MS * 1000000LL;
}
if (mFormat == AUDIO_FORMAT_PCM_8_BIT && !(mFlags & AUDIO_OUTPUT_FLAG_DIRECT)) {
// 8 to 16 bit conversion, note that source and destination are the same address
memcpy_to_i16_from_u8(audioBuffer.i16, (const uint8_t *) audioBuffer.i8, writtenSize);
- writtenSize <<= 1;
+ audioBuffer.size <<= 1;
}
- audioBuffer.size = writtenSize;
- // NOTE: cblk->frameSize is not equal to AudioTrack::frameSize() for
- // 8 bit PCM data: in this case, cblk->frameSize is based on a sample size of
- // 16 bit.
- audioBuffer.frameCount = writtenSize / mFrameSizeAF;
-
- frames -= audioBuffer.frameCount;
+ size_t releasedFrames = audioBuffer.size / mFrameSizeAF;
+ audioBuffer.frameCount = releasedFrames;
+ mRemainingFrames -= releasedFrames;
+ if (misalignment >= releasedFrames) {
+ misalignment -= releasedFrames;
+ } else {
+ misalignment = 0;
+ }
releaseBuffer(&audioBuffer);
- }
- while (frames);
- if (frames == 0) {
- mRemainingFrames = mNotificationFramesAct;
- } else {
- mRemainingFrames = frames;
+ // FIXME here is where we would repeat EVENT_MORE_DATA again on same advanced buffer
+ // if callback doesn't like to accept the full chunk
+ if (writtenSize < reqSize) {
+ continue;
+ }
+
+ // There could be enough non-contiguous frames available to satisfy the remaining request
+ if (mRemainingFrames <= nonContig) {
+ continue;
+ }
+
+#if 0
+ // This heuristic tries to collapse a series of EVENT_MORE_DATA that would total to a
+ // sum <= notificationFrames. It replaces that series by at most two EVENT_MORE_DATA
+ // that total to a sum == notificationFrames.
+ if (0 < misalignment && misalignment <= mRemainingFrames) {
+ mRemainingFrames = misalignment;
+ return (mRemainingFrames * 1100000000LL) / sampleRate;
+ }
+#endif
+
}
- return true;
+ mRemainingFrames = notificationFrames;
+ mRetryOnPartialBuffer = true;
+
+ // A lot has transpired since ns was calculated, so run again immediately and re-calculate
+ return 0;
}
-// must be called with mLock and refCblk.lock held. Callers must also hold strong references on
-// the IAudioTrack and IMemory in case they are recreated here.
-// If the IAudioTrack is successfully restored, the refCblk pointer is updated
-// FIXME Don't depend on caller to hold strong references.
-status_t AudioTrack::restoreTrack_l(audio_track_cblk_t*& refCblk, bool fromStart)
+status_t AudioTrack::restoreTrack_l(const char *from)
{
+ ALOGW("dead IAudioTrack, %s, creating a new one from %s()",
+ isOffloaded() ? "Offloaded" : "PCM", from);
+ ++mSequence;
status_t result;
- audio_track_cblk_t* cblk = refCblk;
- audio_track_cblk_t* newCblk = cblk;
- ALOGW("dead IAudioTrack, creating a new one from %s",
- fromStart ? "start()" : "obtainBuffer()");
-
- // signal old cblk condition so that other threads waiting for available buffers stop
- // waiting now
- cblk->cv.broadcast();
- cblk->lock.unlock();
-
// refresh the audio configuration cache in this process to make sure we get new
// output parameters in getOutput_l() and createTrack_l()
AudioSystem::clearAudioConfigCache();
+ if (isOffloaded()) {
+ return DEAD_OBJECT;
+ }
+
+ // force new output query from audio policy manager;
+ mOutput = 0;
+ audio_io_handle_t output = getOutput_l();
+
// if the new IAudioTrack is created, createTrack_l() will modify the
// following member variables: mAudioTrack, mCblkMemory and mCblk.
// It will also delete the strong references on previous IAudioTrack and IMemory
+
+ // take the frames that will be lost by track recreation into account in saved position
+ size_t position = mProxy->getPosition() + mProxy->getFramesFilled();
+ mNewPosition = position + mUpdatePeriod;
+ size_t bufferPosition = mStaticProxy != NULL ? mStaticProxy->getBufferPosition() : 0;
result = createTrack_l(mStreamType,
mSampleRate,
mFormat,
mReqFrameCount, // so that frame count never goes down
mFlags,
mSharedBuffer,
- getOutput_l());
+ output,
+ position /*epoch*/);
if (result == NO_ERROR) {
- uint32_t user = cblk->user;
- uint32_t server = cblk->server;
+ // continue playback from last known position, but
+ // don't attempt to restore loop after invalidation; it's difficult and not worthwhile
+ if (mStaticProxy != NULL) {
+ mLoopPeriod = 0;
+ mStaticProxy->setLoop(bufferPosition, mFrameCount, 0);
+ }
+ // FIXME How do we simulate the fact that all frames present in the buffer at the time of
+ // track destruction have been played? This is critical for SoundPool implementation
+ // This must be broken, and needs to be tested/debugged.
+#if 0
// restore write index and set other indexes to reflect empty buffer status
- newCblk = mCblk;
- newCblk->user = user;
- newCblk->server = user;
- newCblk->userBase = user;
- newCblk->serverBase = user;
- // restore loop: this is not guaranteed to succeed if new frame count is not
- // compatible with loop length
- setLoop_l(cblk->loopStart, cblk->loopEnd, cblk->loopCount);
- size_t frames = 0;
- if (!fromStart) {
- newCblk->bufferTimeoutMs = MAX_RUN_TIMEOUT_MS;
+ if (!strcmp(from, "start")) {
// Make sure that a client relying on callback events indicating underrun or
// the actual amount of audio frames played (e.g SoundPool) receives them.
if (mSharedBuffer == 0) {
- if (user > server) {
- frames = ((user - server) > mFrameCount) ?
- mFrameCount : (user - server);
- memset(mBuffers, 0, frames * mFrameSizeAF);
- }
// restart playback even if buffer is not completely filled.
- android_atomic_or(CBLK_FORCEREADY, &newCblk->flags);
+ android_atomic_or(CBLK_FORCEREADY, &mCblk->mFlags);
}
}
- if (mSharedBuffer != 0) {
- frames = mFrameCount;
- }
- if (frames > 0) {
- // stepUser() clears CBLK_UNDERRUN flag enabling underrun callbacks to
- // the client
- mProxy->stepUser(frames);
- }
- if (mActive) {
+#endif
+ if (mState == STATE_ACTIVE) {
result = mAudioTrack->start();
- ALOGW_IF(result != NO_ERROR, "restoreTrack_l() start() failed status %d", result);
- }
- if (fromStart && result == NO_ERROR) {
- mNewPosition = newCblk->server + mUpdatePeriod;
}
}
- ALOGW_IF(result != NO_ERROR, "restoreTrack_l() failed status %d", result);
- ALOGV("restoreTrack_l() status %d mActive %d cblk %p, old cblk %p flags %08x old flags %08x",
- result, mActive, newCblk, cblk, newCblk->flags, cblk->flags);
-
- if (result == NO_ERROR) {
- // from now on we switch to the newly created cblk
- refCblk = newCblk;
+ if (result != NO_ERROR) {
+ //Use of direct and offloaded output streams is ref counted by audio policy manager.
+ // As getOutput was called above and resulted in an output stream to be opened,
+ // we need to release it.
+ AudioSystem::releaseOutput(output);
+ ALOGW("restoreTrack_l() failed status %d", result);
+ mState = STATE_STOPPED;
}
- newCblk->lock.lock();
-
- ALOGW_IF(result != NO_ERROR, "restoreTrack_l() error %d", result);
return result;
}
+status_t AudioTrack::setParameters(const String8& keyValuePairs)
+{
+ AutoMutex lock(mLock);
+ return mAudioTrack->setParameters(keyValuePairs);
+}
+
+status_t AudioTrack::getTimestamp(AudioTimestamp& timestamp)
+{
+ AutoMutex lock(mLock);
+ // FIXME not implemented for fast tracks; should use proxy and SSQ
+ if (mFlags & AUDIO_OUTPUT_FLAG_FAST) {
+ return INVALID_OPERATION;
+ }
+ if (mState != STATE_ACTIVE && mState != STATE_PAUSED) {
+ return INVALID_OPERATION;
+ }
+ status_t status = mAudioTrack->getTimestamp(timestamp);
+ if (status == NO_ERROR) {
+ timestamp.mPosition += mProxy->getEpoch();
+ }
+ return status;
+}
+
+String8 AudioTrack::getParameters(const String8& keys)
+{
+ if (mOutput) {
+ return AudioSystem::getParameters(mOutput, keys);
+ } else {
+ return String8::empty();
+ }
+}
+
status_t AudioTrack::dump(int fd, const Vector<String16>& args) const
{
@@ -1480,16 +1761,34 @@ status_t AudioTrack::dump(int fd, const Vector<String16>& args) const
result.append(buffer);
snprintf(buffer, 255, " sample rate(%u), status(%d)\n", mSampleRate, mStatus);
result.append(buffer);
- snprintf(buffer, 255, " active(%d), latency (%d)\n", mActive, mLatency);
+ snprintf(buffer, 255, " state(%d), latency (%d)\n", mState, mLatency);
result.append(buffer);
::write(fd, result.string(), result.size());
return NO_ERROR;
}
+uint32_t AudioTrack::getUnderrunFrames() const
+{
+ AutoMutex lock(mLock);
+ return mProxy->getUnderrunFrames();
+}
+
+// =========================================================================
+
+void AudioTrack::DeathNotifier::binderDied(const wp<IBinder>& who)
+{
+ sp<AudioTrack> audioTrack = mAudioTrack.promote();
+ if (audioTrack != 0) {
+ AutoMutex lock(audioTrack->mLock);
+ audioTrack->mProxy->binderDied();
+ }
+}
+
// =========================================================================
AudioTrack::AudioTrackThread::AudioTrackThread(AudioTrack& receiver, bool bCanCallJava)
- : Thread(bCanCallJava), mReceiver(receiver), mPaused(true)
+ : Thread(bCanCallJava), mReceiver(receiver), mPaused(true), mPausedInt(false), mPausedNs(0LL),
+ mIgnoreNextPausedInt(false)
{
}
@@ -1506,11 +1805,38 @@ bool AudioTrack::AudioTrackThread::threadLoop()
// caller will check for exitPending()
return true;
}
+ if (mIgnoreNextPausedInt) {
+ mIgnoreNextPausedInt = false;
+ mPausedInt = false;
+ }
+ if (mPausedInt) {
+ if (mPausedNs > 0) {
+ (void) mMyCond.waitRelative(mMyLock, mPausedNs);
+ } else {
+ mMyCond.wait(mMyLock);
+ }
+ mPausedInt = false;
+ return true;
+ }
}
- if (!mReceiver.processAudioBuffer(this)) {
- pause();
+ nsecs_t ns = mReceiver.processAudioBuffer(this);
+ switch (ns) {
+ case 0:
+ return true;
+ case NS_INACTIVE:
+ pauseInternal();
+ return true;
+ case NS_NEVER:
+ return false;
+ case NS_WHENEVER:
+ // FIXME increase poll interval, or make event-driven
+ ns = 1000000000LL;
+ // fall through
+ default:
+ LOG_ALWAYS_FATAL_IF(ns < 0, "processAudioBuffer() returned %lld", ns);
+ pauseInternal(ns);
+ return true;
}
- return true;
}
void AudioTrack::AudioTrackThread::requestExit()
@@ -1529,10 +1855,19 @@ void AudioTrack::AudioTrackThread::pause()
void AudioTrack::AudioTrackThread::resume()
{
AutoMutex _l(mMyLock);
- if (mPaused) {
+ mIgnoreNextPausedInt = true;
+ if (mPaused || mPausedInt) {
mPaused = false;
+ mPausedInt = false;
mMyCond.signal();
}
}
+void AudioTrack::AudioTrackThread::pauseInternal(nsecs_t ns)
+{
+ AutoMutex _l(mMyLock);
+ mPausedInt = true;
+ mPausedNs = ns;
+}
+
}; // namespace android
diff --git a/media/libmedia/AudioTrackShared.cpp b/media/libmedia/AudioTrackShared.cpp
index 13d47c9..da73d65 100644
--- a/media/libmedia/AudioTrackShared.cpp
+++ b/media/libmedia/AudioTrackShared.cpp
@@ -19,178 +19,841 @@
#include <private/media/AudioTrackShared.h>
#include <utils/Log.h>
+extern "C" {
+#include "../private/bionic_futex.h"
+}
namespace android {
audio_track_cblk_t::audio_track_cblk_t()
- : lock(Mutex::SHARED), cv(Condition::SHARED), user(0), server(0),
- userBase(0), serverBase(0), frameCount_(0),
- loopStart(UINT_MAX), loopEnd(UINT_MAX), loopCount(0), mVolumeLR(0x10001000),
- mSampleRate(0), mSendLevel(0), flags(0)
+ : mServer(0), frameCount_(0), mFutex(0), mMinimum(0),
+ mVolumeLR(0x10001000), mSampleRate(0), mSendLevel(0), mFlags(0)
+{
+ memset(&u, 0, sizeof(u));
+}
+
+// ---------------------------------------------------------------------------
+
+Proxy::Proxy(audio_track_cblk_t* cblk, void *buffers, size_t frameCount, size_t frameSize,
+ bool isOut, bool clientInServer)
+ : mCblk(cblk), mBuffers(buffers), mFrameCount(frameCount), mFrameSize(frameSize),
+ mFrameCountP2(roundup(frameCount)), mIsOut(isOut), mClientInServer(clientInServer),
+ mIsShutdown(false), mUnreleased(0)
{
}
-uint32_t audio_track_cblk_t::stepUser(size_t stepCount, size_t frameCount, bool isOut)
+// ---------------------------------------------------------------------------
+
+ClientProxy::ClientProxy(audio_track_cblk_t* cblk, void *buffers, size_t frameCount,
+ size_t frameSize, bool isOut, bool clientInServer)
+ : Proxy(cblk, buffers, frameCount, frameSize, isOut, clientInServer), mEpoch(0)
+{
+}
+
+const struct timespec ClientProxy::kForever = {INT_MAX /*tv_sec*/, 0 /*tv_nsec*/};
+const struct timespec ClientProxy::kNonBlocking = {0 /*tv_sec*/, 0 /*tv_nsec*/};
+
+#define MEASURE_NS 10000000 // attempt to provide accurate timeouts if requested >= MEASURE_NS
+
+// To facilitate quicker recovery from server failure, this value limits the timeout per each futex
+// wait. However it does not protect infinite timeouts. If defined to be zero, there is no limit.
+// FIXME May not be compatible with audio tunneling requirements where timeout should be in the
+// order of minutes.
+#define MAX_SEC 5
+
+status_t ClientProxy::obtainBuffer(Buffer* buffer, const struct timespec *requested,
+ struct timespec *elapsed)
{
- ALOGV("stepuser %08x %08x %d", user, server, stepCount);
+ LOG_ALWAYS_FATAL_IF(buffer == NULL || buffer->mFrameCount == 0);
+ struct timespec total; // total elapsed time spent waiting
+ total.tv_sec = 0;
+ total.tv_nsec = 0;
+ bool measure = elapsed != NULL; // whether to measure total elapsed time spent waiting
- uint32_t u = user;
- u += stepCount;
- // Ensure that user is never ahead of server for AudioRecord
- if (isOut) {
- // If stepServer() has been called once, switch to normal obtainBuffer() timeout period
- if (bufferTimeoutMs == MAX_STARTUP_TIMEOUT_MS-1) {
- bufferTimeoutMs = MAX_RUN_TIMEOUT_MS;
+ status_t status;
+ enum {
+ TIMEOUT_ZERO, // requested == NULL || *requested == 0
+ TIMEOUT_INFINITE, // *requested == infinity
+ TIMEOUT_FINITE, // 0 < *requested < infinity
+ TIMEOUT_CONTINUE, // additional chances after TIMEOUT_FINITE
+ } timeout;
+ if (requested == NULL) {
+ timeout = TIMEOUT_ZERO;
+ } else if (requested->tv_sec == 0 && requested->tv_nsec == 0) {
+ timeout = TIMEOUT_ZERO;
+ } else if (requested->tv_sec == INT_MAX) {
+ timeout = TIMEOUT_INFINITE;
+ } else {
+ timeout = TIMEOUT_FINITE;
+ if (requested->tv_sec > 0 || requested->tv_nsec >= MEASURE_NS) {
+ measure = true;
}
- } else if (u > server) {
- ALOGW("stepUser occurred after track reset");
- u = server;
}
-
- if (u >= frameCount) {
- // common case, user didn't just wrap
- if (u - frameCount >= userBase ) {
- userBase += frameCount;
+ struct timespec before;
+ bool beforeIsValid = false;
+ audio_track_cblk_t* cblk = mCblk;
+ bool ignoreInitialPendingInterrupt = true;
+ // check for shared memory corruption
+ if (mIsShutdown) {
+ status = NO_INIT;
+ goto end;
+ }
+ for (;;) {
+ int32_t flags = android_atomic_and(~CBLK_INTERRUPT, &cblk->mFlags);
+ // check for track invalidation by server, or server death detection
+ if (flags & CBLK_INVALID) {
+ ALOGV("Track invalidated");
+ status = DEAD_OBJECT;
+ goto end;
+ }
+ // check for obtainBuffer interrupted by client
+ if (!ignoreInitialPendingInterrupt && (flags & CBLK_INTERRUPT)) {
+ ALOGV("obtainBuffer() interrupted by client");
+ status = -EINTR;
+ goto end;
+ }
+ ignoreInitialPendingInterrupt = false;
+ // compute number of frames available to write (AudioTrack) or read (AudioRecord)
+ int32_t front;
+ int32_t rear;
+ if (mIsOut) {
+ // The barrier following the read of mFront is probably redundant.
+ // We're about to perform a conditional branch based on 'filled',
+ // which will force the processor to observe the read of mFront
+ // prior to allowing data writes starting at mRaw.
+ // However, the processor may support speculative execution,
+ // and be unable to undo speculative writes into shared memory.
+ // The barrier will prevent such speculative execution.
+ front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront);
+ rear = cblk->u.mStreaming.mRear;
+ } else {
+ // On the other hand, this barrier is required.
+ rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear);
+ front = cblk->u.mStreaming.mFront;
+ }
+ ssize_t filled = rear - front;
+ // pipe should not be overfull
+ if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
+ ALOGE("Shared memory control block is corrupt (filled=%d); shutting down", filled);
+ mIsShutdown = true;
+ status = NO_INIT;
+ goto end;
+ }
+ // don't allow filling pipe beyond the nominal size
+ size_t avail = mIsOut ? mFrameCount - filled : filled;
+ if (avail > 0) {
+ // 'avail' may be non-contiguous, so return only the first contiguous chunk
+ size_t part1;
+ if (mIsOut) {
+ rear &= mFrameCountP2 - 1;
+ part1 = mFrameCountP2 - rear;
+ } else {
+ front &= mFrameCountP2 - 1;
+ part1 = mFrameCountP2 - front;
+ }
+ if (part1 > avail) {
+ part1 = avail;
+ }
+ if (part1 > buffer->mFrameCount) {
+ part1 = buffer->mFrameCount;
+ }
+ buffer->mFrameCount = part1;
+ buffer->mRaw = part1 > 0 ?
+ &((char *) mBuffers)[(mIsOut ? rear : front) * mFrameSize] : NULL;
+ buffer->mNonContig = avail - part1;
+ mUnreleased = part1;
+ status = NO_ERROR;
+ break;
+ }
+ struct timespec remaining;
+ const struct timespec *ts;
+ switch (timeout) {
+ case TIMEOUT_ZERO:
+ status = WOULD_BLOCK;
+ goto end;
+ case TIMEOUT_INFINITE:
+ ts = NULL;
+ break;
+ case TIMEOUT_FINITE:
+ timeout = TIMEOUT_CONTINUE;
+ if (MAX_SEC == 0) {
+ ts = requested;
+ break;
+ }
+ // fall through
+ case TIMEOUT_CONTINUE:
+ // FIXME we do not retry if requested < 10ms? needs documentation on this state machine
+ if (!measure || requested->tv_sec < total.tv_sec ||
+ (requested->tv_sec == total.tv_sec && requested->tv_nsec <= total.tv_nsec)) {
+ status = TIMED_OUT;
+ goto end;
+ }
+ remaining.tv_sec = requested->tv_sec - total.tv_sec;
+ if ((remaining.tv_nsec = requested->tv_nsec - total.tv_nsec) < 0) {
+ remaining.tv_nsec += 1000000000;
+ remaining.tv_sec++;
+ }
+ if (0 < MAX_SEC && MAX_SEC < remaining.tv_sec) {
+ remaining.tv_sec = MAX_SEC;
+ remaining.tv_nsec = 0;
+ }
+ ts = &remaining;
+ break;
+ default:
+ LOG_FATAL("obtainBuffer() timeout=%d", timeout);
+ ts = NULL;
+ break;
+ }
+ int32_t old = android_atomic_and(~CBLK_FUTEX_WAKE, &cblk->mFutex);
+ if (!(old & CBLK_FUTEX_WAKE)) {
+ int rc;
+ if (measure && !beforeIsValid) {
+ clock_gettime(CLOCK_MONOTONIC, &before);
+ beforeIsValid = true;
+ }
+ int ret = __futex_syscall4(&cblk->mFutex,
+ mClientInServer ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, old & ~CBLK_FUTEX_WAKE, ts);
+ // update total elapsed time spent waiting
+ if (measure) {
+ struct timespec after;
+ clock_gettime(CLOCK_MONOTONIC, &after);
+ total.tv_sec += after.tv_sec - before.tv_sec;
+ long deltaNs = after.tv_nsec - before.tv_nsec;
+ if (deltaNs < 0) {
+ deltaNs += 1000000000;
+ total.tv_sec--;
+ }
+ if ((total.tv_nsec += deltaNs) >= 1000000000) {
+ total.tv_nsec -= 1000000000;
+ total.tv_sec++;
+ }
+ before = after;
+ beforeIsValid = true;
+ }
+ switch (ret) {
+ case 0: // normal wakeup by server, or by binderDied()
+ case -EWOULDBLOCK: // benign race condition with server
+ case -EINTR: // wait was interrupted by signal or other spurious wakeup
+ case -ETIMEDOUT: // time-out expired
+ // FIXME these error/non-0 status are being dropped
+ break;
+ default:
+ ALOGE("%s unexpected error %d", __func__, ret);
+ status = -ret;
+ goto end;
+ }
}
- } else if (u >= userBase + frameCount) {
- // user just wrapped
- userBase += frameCount;
}
- user = u;
+end:
+ if (status != NO_ERROR) {
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
+ mUnreleased = 0;
+ }
+ if (elapsed != NULL) {
+ *elapsed = total;
+ }
+ if (requested == NULL) {
+ requested = &kNonBlocking;
+ }
+ if (measure) {
+ ALOGV("requested %ld.%03ld elapsed %ld.%03ld",
+ requested->tv_sec, requested->tv_nsec / 1000000,
+ total.tv_sec, total.tv_nsec / 1000000);
+ }
+ return status;
+}
+
+void ClientProxy::releaseBuffer(Buffer* buffer)
+{
+ LOG_ALWAYS_FATAL_IF(buffer == NULL);
+ size_t stepCount = buffer->mFrameCount;
+ if (stepCount == 0 || mIsShutdown) {
+ // prevent accidental re-use of buffer
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
+ return;
+ }
+ LOG_ALWAYS_FATAL_IF(!(stepCount <= mUnreleased && mUnreleased <= mFrameCount));
+ mUnreleased -= stepCount;
+ audio_track_cblk_t* cblk = mCblk;
+ // Both of these barriers are required
+ if (mIsOut) {
+ int32_t rear = cblk->u.mStreaming.mRear;
+ android_atomic_release_store(stepCount + rear, &cblk->u.mStreaming.mRear);
+ } else {
+ int32_t front = cblk->u.mStreaming.mFront;
+ android_atomic_release_store(stepCount + front, &cblk->u.mStreaming.mFront);
+ }
+}
- // Clear flow control error condition as new data has been written/read to/from buffer.
- if (flags & CBLK_UNDERRUN) {
- android_atomic_and(~CBLK_UNDERRUN, &flags);
+void ClientProxy::binderDied()
+{
+ audio_track_cblk_t* cblk = mCblk;
+ if (!(android_atomic_or(CBLK_INVALID, &cblk->mFlags) & CBLK_INVALID)) {
+ // it seems that a FUTEX_WAKE_PRIVATE will not wake a FUTEX_WAIT, even within same process
+ (void) __futex_syscall3(&cblk->mFutex, mClientInServer ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE,
+ 1);
}
+}
- return u;
+void ClientProxy::interrupt()
+{
+ audio_track_cblk_t* cblk = mCblk;
+ if (!(android_atomic_or(CBLK_INTERRUPT, &cblk->mFlags) & CBLK_INTERRUPT)) {
+ (void) __futex_syscall3(&cblk->mFutex, mClientInServer ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE,
+ 1);
+ }
}
-bool audio_track_cblk_t::stepServer(size_t stepCount, size_t frameCount, bool isOut)
+size_t ClientProxy::getMisalignment()
{
- ALOGV("stepserver %08x %08x %d", user, server, stepCount);
+ audio_track_cblk_t* cblk = mCblk;
+ return (mFrameCountP2 - (mIsOut ? cblk->u.mStreaming.mRear : cblk->u.mStreaming.mFront)) &
+ (mFrameCountP2 - 1);
+}
+
+size_t ClientProxy::getFramesFilled() {
+ audio_track_cblk_t* cblk = mCblk;
+ int32_t front;
+ int32_t rear;
- if (!tryLock()) {
- ALOGW("stepServer() could not lock cblk");
- return false;
+ if (mIsOut) {
+ front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront);
+ rear = cblk->u.mStreaming.mRear;
+ } else {
+ rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear);
+ front = cblk->u.mStreaming.mFront;
+ }
+ ssize_t filled = rear - front;
+ // pipe should not be overfull
+ if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
+ ALOGE("Shared memory control block is corrupt (filled=%d); shutting down", filled);
+ return 0;
}
+ return (size_t)filled;
+}
+
+// ---------------------------------------------------------------------------
+
+void AudioTrackClientProxy::flush()
+{
+ mCblk->u.mStreaming.mFlush++;
+}
- uint32_t s = server;
- bool flushed = (s == user);
+bool AudioTrackClientProxy::clearStreamEndDone() {
+ return (android_atomic_and(~CBLK_STREAM_END_DONE, &mCblk->mFlags) & CBLK_STREAM_END_DONE) != 0;
+}
+
+bool AudioTrackClientProxy::getStreamEndDone() const {
+ return (mCblk->mFlags & CBLK_STREAM_END_DONE) != 0;
+}
- s += stepCount;
- if (isOut) {
- // Mark that we have read the first buffer so that next time stepUser() is called
- // we switch to normal obtainBuffer() timeout period
- if (bufferTimeoutMs == MAX_STARTUP_TIMEOUT_MS) {
- bufferTimeoutMs = MAX_STARTUP_TIMEOUT_MS - 1;
+status_t AudioTrackClientProxy::waitStreamEndDone(const struct timespec *requested)
+{
+ struct timespec total; // total elapsed time spent waiting
+ total.tv_sec = 0;
+ total.tv_nsec = 0;
+ audio_track_cblk_t* cblk = mCblk;
+ status_t status;
+ enum {
+ TIMEOUT_ZERO, // requested == NULL || *requested == 0
+ TIMEOUT_INFINITE, // *requested == infinity
+ TIMEOUT_FINITE, // 0 < *requested < infinity
+ TIMEOUT_CONTINUE, // additional chances after TIMEOUT_FINITE
+ } timeout;
+ if (requested == NULL) {
+ timeout = TIMEOUT_ZERO;
+ } else if (requested->tv_sec == 0 && requested->tv_nsec == 0) {
+ timeout = TIMEOUT_ZERO;
+ } else if (requested->tv_sec == INT_MAX) {
+ timeout = TIMEOUT_INFINITE;
+ } else {
+ timeout = TIMEOUT_FINITE;
+ }
+ for (;;) {
+ int32_t flags = android_atomic_and(~(CBLK_INTERRUPT|CBLK_STREAM_END_DONE), &cblk->mFlags);
+ // check for track invalidation by server, or server death detection
+ if (flags & CBLK_INVALID) {
+ ALOGV("Track invalidated");
+ status = DEAD_OBJECT;
+ goto end;
+ }
+ if (flags & CBLK_STREAM_END_DONE) {
+ ALOGV("stream end received");
+ status = NO_ERROR;
+ goto end;
+ }
+ // check for obtainBuffer interrupted by client
+ // check for obtainBuffer interrupted by client
+ if (flags & CBLK_INTERRUPT) {
+ ALOGV("waitStreamEndDone() interrupted by client");
+ status = -EINTR;
+ goto end;
}
- // It is possible that we receive a flush()
- // while the mixer is processing a block: in this case,
- // stepServer() is called After the flush() has reset u & s and
- // we have s > u
- if (flushed) {
- ALOGW("stepServer occurred after track reset");
- s = user;
+ struct timespec remaining;
+ const struct timespec *ts;
+ switch (timeout) {
+ case TIMEOUT_ZERO:
+ status = WOULD_BLOCK;
+ goto end;
+ case TIMEOUT_INFINITE:
+ ts = NULL;
+ break;
+ case TIMEOUT_FINITE:
+ timeout = TIMEOUT_CONTINUE;
+ if (MAX_SEC == 0) {
+ ts = requested;
+ break;
+ }
+ // fall through
+ case TIMEOUT_CONTINUE:
+ // FIXME we do not retry if requested < 10ms? needs documentation on this state machine
+ if (requested->tv_sec < total.tv_sec ||
+ (requested->tv_sec == total.tv_sec && requested->tv_nsec <= total.tv_nsec)) {
+ status = TIMED_OUT;
+ goto end;
+ }
+ remaining.tv_sec = requested->tv_sec - total.tv_sec;
+ if ((remaining.tv_nsec = requested->tv_nsec - total.tv_nsec) < 0) {
+ remaining.tv_nsec += 1000000000;
+ remaining.tv_sec++;
+ }
+ if (0 < MAX_SEC && MAX_SEC < remaining.tv_sec) {
+ remaining.tv_sec = MAX_SEC;
+ remaining.tv_nsec = 0;
+ }
+ ts = &remaining;
+ break;
+ default:
+ LOG_FATAL("waitStreamEndDone() timeout=%d", timeout);
+ ts = NULL;
+ break;
}
+ int32_t old = android_atomic_and(~CBLK_FUTEX_WAKE, &cblk->mFutex);
+ if (!(old & CBLK_FUTEX_WAKE)) {
+ int rc;
+ int ret = __futex_syscall4(&cblk->mFutex,
+ mClientInServer ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, old & ~CBLK_FUTEX_WAKE, ts);
+ switch (ret) {
+ case 0: // normal wakeup by server, or by binderDied()
+ case -EWOULDBLOCK: // benign race condition with server
+ case -EINTR: // wait was interrupted by signal or other spurious wakeup
+ case -ETIMEDOUT: // time-out expired
+ break;
+ default:
+ ALOGE("%s unexpected error %d", __func__, ret);
+ status = -ret;
+ goto end;
+ }
+ }
+ }
+
+end:
+ if (requested == NULL) {
+ requested = &kNonBlocking;
}
+ return status;
+}
+
+// ---------------------------------------------------------------------------
- if (s >= loopEnd) {
- ALOGW_IF(s > loopEnd, "stepServer: s %u > loopEnd %u", s, loopEnd);
- s = loopStart;
- if (--loopCount == 0) {
- loopEnd = UINT_MAX;
- loopStart = UINT_MAX;
+StaticAudioTrackClientProxy::StaticAudioTrackClientProxy(audio_track_cblk_t* cblk, void *buffers,
+ size_t frameCount, size_t frameSize)
+ : AudioTrackClientProxy(cblk, buffers, frameCount, frameSize),
+ mMutator(&cblk->u.mStatic.mSingleStateQueue), mBufferPosition(0)
+{
+}
+
+void StaticAudioTrackClientProxy::flush()
+{
+ LOG_FATAL("static flush");
+}
+
+void StaticAudioTrackClientProxy::setLoop(size_t loopStart, size_t loopEnd, int loopCount)
+{
+ StaticAudioTrackState newState;
+ newState.mLoopStart = loopStart;
+ newState.mLoopEnd = loopEnd;
+ newState.mLoopCount = loopCount;
+ mBufferPosition = loopStart;
+ (void) mMutator.push(newState);
+}
+
+size_t StaticAudioTrackClientProxy::getBufferPosition()
+{
+ size_t bufferPosition;
+ if (mMutator.ack()) {
+ bufferPosition = mCblk->u.mStatic.mBufferPosition;
+ if (bufferPosition > mFrameCount) {
+ bufferPosition = mFrameCount;
}
+ } else {
+ bufferPosition = mBufferPosition;
}
+ return bufferPosition;
+}
+
+// ---------------------------------------------------------------------------
+
+ServerProxy::ServerProxy(audio_track_cblk_t* cblk, void *buffers, size_t frameCount,
+ size_t frameSize, bool isOut, bool clientInServer)
+ : Proxy(cblk, buffers, frameCount, frameSize, isOut, clientInServer),
+ mAvailToClient(0), mFlush(0)
+{
+}
- if (s >= frameCount) {
- // common case, server didn't just wrap
- if (s - frameCount >= serverBase ) {
- serverBase += frameCount;
+status_t ServerProxy::obtainBuffer(Buffer* buffer)
+{
+ LOG_ALWAYS_FATAL_IF(buffer == NULL || buffer->mFrameCount == 0);
+ if (mIsShutdown) {
+ goto no_init;
+ }
+ {
+ audio_track_cblk_t* cblk = mCblk;
+ // compute number of frames available to write (AudioTrack) or read (AudioRecord),
+ // or use previous cached value from framesReady(), with added barrier if it omits.
+ int32_t front;
+ int32_t rear;
+ // See notes on barriers at ClientProxy::obtainBuffer()
+ if (mIsOut) {
+ int32_t flush = cblk->u.mStreaming.mFlush;
+ rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear);
+ front = cblk->u.mStreaming.mFront;
+ if (flush != mFlush) {
+ mFlush = flush;
+ // effectively obtain then release whatever is in the buffer
+ android_atomic_release_store(rear, &cblk->u.mStreaming.mFront);
+ if (front != rear) {
+ int32_t old = android_atomic_or(CBLK_FUTEX_WAKE, &cblk->mFutex);
+ if (!(old & CBLK_FUTEX_WAKE)) {
+ (void) __futex_syscall3(&cblk->mFutex,
+ mClientInServer ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE, 1);
+ }
+ }
+ front = rear;
}
- } else if (s >= serverBase + frameCount) {
- // server just wrapped
- serverBase += frameCount;
+ } else {
+ front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront);
+ rear = cblk->u.mStreaming.mRear;
+ }
+ ssize_t filled = rear - front;
+ // pipe should not already be overfull
+ if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
+ ALOGE("Shared memory control block is corrupt (filled=%d); shutting down", filled);
+ mIsShutdown = true;
+ }
+ if (mIsShutdown) {
+ goto no_init;
+ }
+ // don't allow filling pipe beyond the nominal size
+ size_t availToServer;
+ if (mIsOut) {
+ availToServer = filled;
+ mAvailToClient = mFrameCount - filled;
+ } else {
+ availToServer = mFrameCount - filled;
+ mAvailToClient = filled;
}
+ // 'availToServer' may be non-contiguous, so return only the first contiguous chunk
+ size_t part1;
+ if (mIsOut) {
+ front &= mFrameCountP2 - 1;
+ part1 = mFrameCountP2 - front;
+ } else {
+ rear &= mFrameCountP2 - 1;
+ part1 = mFrameCountP2 - rear;
+ }
+ if (part1 > availToServer) {
+ part1 = availToServer;
+ }
+ size_t ask = buffer->mFrameCount;
+ if (part1 > ask) {
+ part1 = ask;
+ }
+ // is assignment redundant in some cases?
+ buffer->mFrameCount = part1;
+ buffer->mRaw = part1 > 0 ?
+ &((char *) mBuffers)[(mIsOut ? front : rear) * mFrameSize] : NULL;
+ buffer->mNonContig = availToServer - part1;
+ mUnreleased = part1;
+ return part1 > 0 ? NO_ERROR : WOULD_BLOCK;
+ }
+no_init:
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
+ mUnreleased = 0;
+ return NO_INIT;
+}
- server = s;
+void ServerProxy::releaseBuffer(Buffer* buffer)
+{
+ LOG_ALWAYS_FATAL_IF(buffer == NULL);
+ size_t stepCount = buffer->mFrameCount;
+ if (stepCount == 0 || mIsShutdown) {
+ // prevent accidental re-use of buffer
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
+ return;
+ }
+ LOG_ALWAYS_FATAL_IF(!(stepCount <= mUnreleased && mUnreleased <= mFrameCount));
+ mUnreleased -= stepCount;
+ audio_track_cblk_t* cblk = mCblk;
+ if (mIsOut) {
+ int32_t front = cblk->u.mStreaming.mFront;
+ android_atomic_release_store(stepCount + front, &cblk->u.mStreaming.mFront);
+ } else {
+ int32_t rear = cblk->u.mStreaming.mRear;
+ android_atomic_release_store(stepCount + rear, &cblk->u.mStreaming.mRear);
+ }
- if (!(flags & CBLK_INVALID)) {
- cv.signal();
+ mCblk->mServer += stepCount;
+
+ size_t half = mFrameCount / 2;
+ if (half == 0) {
+ half = 1;
+ }
+ size_t minimum = cblk->mMinimum;
+ if (minimum == 0) {
+ minimum = mIsOut ? half : 1;
+ } else if (minimum > half) {
+ minimum = half;
}
- lock.unlock();
- return true;
+ // FIXME AudioRecord wakeup needs to be optimized; it currently wakes up client every time
+ if (!mIsOut || (mAvailToClient + stepCount >= minimum)) {
+ ALOGV("mAvailToClient=%u stepCount=%u minimum=%u", mAvailToClient, stepCount, minimum);
+ int32_t old = android_atomic_or(CBLK_FUTEX_WAKE, &cblk->mFutex);
+ if (!(old & CBLK_FUTEX_WAKE)) {
+ (void) __futex_syscall3(&cblk->mFutex,
+ mClientInServer ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE, 1);
+ }
+ }
+
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
}
-void* audio_track_cblk_t::buffer(void *buffers, size_t frameSize, uint32_t offset) const
+// ---------------------------------------------------------------------------
+
+size_t AudioTrackServerProxy::framesReady()
{
- return (int8_t *)buffers + (offset - userBase) * frameSize;
+ LOG_ALWAYS_FATAL_IF(!mIsOut);
+
+ if (mIsShutdown) {
+ return 0;
+ }
+ audio_track_cblk_t* cblk = mCblk;
+
+ int32_t flush = cblk->u.mStreaming.mFlush;
+ if (flush != mFlush) {
+ return mFrameCount;
+ }
+ // the acquire might not be necessary since not doing a subsequent read
+ int32_t rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear);
+ ssize_t filled = rear - cblk->u.mStreaming.mFront;
+ // pipe should not already be overfull
+ if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
+ ALOGE("Shared memory control block is corrupt (filled=%d); shutting down", filled);
+ mIsShutdown = true;
+ return 0;
+ }
+ // cache this value for later use by obtainBuffer(), with added barrier
+ // and racy if called by normal mixer thread
+ // ignores flush(), so framesReady() may report a larger mFrameCount than obtainBuffer()
+ return filled;
+}
+
+bool AudioTrackServerProxy::setStreamEndDone() {
+ bool old =
+ (android_atomic_or(CBLK_STREAM_END_DONE, &mCblk->mFlags) & CBLK_STREAM_END_DONE) != 0;
+ if (!old) {
+ (void) __futex_syscall3(&mCblk->mFutex, mClientInServer ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE,
+ 1);
+ }
+ return old;
}
-uint32_t audio_track_cblk_t::framesAvailable(size_t frameCount, bool isOut)
+void AudioTrackServerProxy::tallyUnderrunFrames(uint32_t frameCount)
{
- Mutex::Autolock _l(lock);
- return framesAvailable_l(frameCount, isOut);
+ mCblk->u.mStreaming.mUnderrunFrames += frameCount;
+
+ // FIXME also wake futex so that underrun is noticed more quickly
+ (void) android_atomic_or(CBLK_UNDERRUN, &mCblk->mFlags);
}
-uint32_t audio_track_cblk_t::framesAvailable_l(size_t frameCount, bool isOut)
+// ---------------------------------------------------------------------------
+
+StaticAudioTrackServerProxy::StaticAudioTrackServerProxy(audio_track_cblk_t* cblk, void *buffers,
+ size_t frameCount, size_t frameSize)
+ : AudioTrackServerProxy(cblk, buffers, frameCount, frameSize),
+ mObserver(&cblk->u.mStatic.mSingleStateQueue), mPosition(0),
+ mEnd(frameCount), mFramesReadyIsCalledByMultipleThreads(false)
{
- uint32_t u = user;
- uint32_t s = server;
+ mState.mLoopStart = 0;
+ mState.mLoopEnd = 0;
+ mState.mLoopCount = 0;
+}
- if (isOut) {
- uint32_t limit = (s < loopStart) ? s : loopStart;
- return limit + frameCount - u;
- } else {
- return frameCount + u - s;
- }
+void StaticAudioTrackServerProxy::framesReadyIsCalledByMultipleThreads()
+{
+ mFramesReadyIsCalledByMultipleThreads = true;
}
-uint32_t audio_track_cblk_t::framesReady(bool isOut)
+size_t StaticAudioTrackServerProxy::framesReady()
{
- uint32_t u = user;
- uint32_t s = server;
+ // FIXME
+ // This is racy if called by normal mixer thread,
+ // as we're reading 2 independent variables without a lock.
+ // Can't call mObserver.poll(), as we might be called from wrong thread.
+ // If looping is enabled, should return a higher number (since includes non-contiguous).
+ size_t position = mPosition;
+ if (!mFramesReadyIsCalledByMultipleThreads) {
+ ssize_t positionOrStatus = pollPosition();
+ if (positionOrStatus >= 0) {
+ position = (size_t) positionOrStatus;
+ }
+ }
+ size_t end = mEnd;
+ return position < end ? end - position : 0;
+}
- if (isOut) {
- if (u < loopEnd) {
- return u - s;
- } else {
- // do not block on mutex shared with client on AudioFlinger side
- if (!tryLock()) {
- ALOGW("framesReady() could not lock cblk");
- return 0;
+ssize_t StaticAudioTrackServerProxy::pollPosition()
+{
+ size_t position = mPosition;
+ StaticAudioTrackState state;
+ if (mObserver.poll(state)) {
+ bool valid = false;
+ size_t loopStart = state.mLoopStart;
+ size_t loopEnd = state.mLoopEnd;
+ if (state.mLoopCount == 0) {
+ if (loopStart > mFrameCount) {
+ loopStart = mFrameCount;
}
- uint32_t frames = UINT_MAX;
- if (loopCount >= 0) {
- frames = (loopEnd - loopStart)*loopCount + u - s;
+ // ignore loopEnd
+ mPosition = position = loopStart;
+ mEnd = mFrameCount;
+ mState.mLoopCount = 0;
+ valid = true;
+ } else {
+ if (loopStart < loopEnd && loopEnd <= mFrameCount &&
+ loopEnd - loopStart >= MIN_LOOP) {
+ if (!(loopStart <= position && position < loopEnd)) {
+ mPosition = position = loopStart;
+ }
+ mEnd = loopEnd;
+ mState = state;
+ valid = true;
}
- lock.unlock();
- return frames;
}
- } else {
- return s - u;
+ if (!valid) {
+ ALOGE("%s client pushed an invalid state, shutting down", __func__);
+ mIsShutdown = true;
+ return (ssize_t) NO_INIT;
+ }
+ mCblk->u.mStatic.mBufferPosition = position;
}
+ return (ssize_t) position;
}
-bool audio_track_cblk_t::tryLock()
+status_t StaticAudioTrackServerProxy::obtainBuffer(Buffer* buffer)
{
- // the code below simulates lock-with-timeout
- // we MUST do this to protect the AudioFlinger server
- // as this lock is shared with the client.
- status_t err;
+ if (mIsShutdown) {
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
+ mUnreleased = 0;
+ return NO_INIT;
+ }
+ ssize_t positionOrStatus = pollPosition();
+ if (positionOrStatus < 0) {
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
+ mUnreleased = 0;
+ return (status_t) positionOrStatus;
+ }
+ size_t position = (size_t) positionOrStatus;
+ size_t avail;
+ if (position < mEnd) {
+ avail = mEnd - position;
+ size_t wanted = buffer->mFrameCount;
+ if (avail < wanted) {
+ buffer->mFrameCount = avail;
+ } else {
+ avail = wanted;
+ }
+ buffer->mRaw = &((char *) mBuffers)[position * mFrameSize];
+ } else {
+ avail = 0;
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ }
+ buffer->mNonContig = 0; // FIXME should be > 0 for looping
+ mUnreleased = avail;
+ return NO_ERROR;
+}
- err = lock.tryLock();
- if (err == -EBUSY) { // just wait a bit
- usleep(1000);
- err = lock.tryLock();
+void StaticAudioTrackServerProxy::releaseBuffer(Buffer* buffer)
+{
+ size_t stepCount = buffer->mFrameCount;
+ LOG_ALWAYS_FATAL_IF(!(stepCount <= mUnreleased));
+ if (stepCount == 0) {
+ // prevent accidental re-use of buffer
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
+ return;
}
- if (err != NO_ERROR) {
- // probably, the client just died.
- return false;
+ mUnreleased -= stepCount;
+ audio_track_cblk_t* cblk = mCblk;
+ size_t position = mPosition;
+ size_t newPosition = position + stepCount;
+ int32_t setFlags = 0;
+ if (!(position <= newPosition && newPosition <= mFrameCount)) {
+ ALOGW("%s newPosition %u outside [%u, %u]", __func__, newPosition, position, mFrameCount);
+ newPosition = mFrameCount;
+ } else if (mState.mLoopCount != 0 && newPosition == mState.mLoopEnd) {
+ if (mState.mLoopCount == -1 || --mState.mLoopCount != 0) {
+ newPosition = mState.mLoopStart;
+ setFlags = CBLK_LOOP_CYCLE;
+ } else {
+ mEnd = mFrameCount; // this is what allows playback to continue after the loop
+ setFlags = CBLK_LOOP_FINAL;
+ }
}
- return true;
+ if (newPosition == mFrameCount) {
+ setFlags |= CBLK_BUFFER_END;
+ }
+ mPosition = newPosition;
+
+ cblk->mServer += stepCount;
+ cblk->u.mStatic.mBufferPosition = newPosition;
+ if (setFlags != 0) {
+ (void) android_atomic_or(setFlags, &cblk->mFlags);
+ // this would be a good place to wake a futex
+ }
+
+ buffer->mFrameCount = 0;
+ buffer->mRaw = NULL;
+ buffer->mNonContig = 0;
}
+void StaticAudioTrackServerProxy::tallyUnderrunFrames(uint32_t frameCount)
+{
+ // Unlike AudioTrackServerProxy::tallyUnderrunFrames() used for streaming tracks,
+ // we don't have a location to count underrun frames. The underrun frame counter
+ // only exists in AudioTrackSharedStreaming. Fortunately, underruns are not
+ // possible for static buffer tracks other than at end of buffer, so this is not a loss.
+
+ // FIXME also wake futex so that underrun is noticed more quickly
+ (void) android_atomic_or(CBLK_UNDERRUN, &mCblk->mFlags);
+}
+
+// ---------------------------------------------------------------------------
+
} // namespace android
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 2f18680..448a82e 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -73,6 +73,7 @@ enum {
LOAD_HW_MODULE,
GET_PRIMARY_OUTPUT_SAMPLING_RATE,
GET_PRIMARY_OUTPUT_FRAME_COUNT,
+ SET_LOW_RAM_DEVICE,
};
class BpAudioFlinger : public BpInterface<IAudioFlinger>
@@ -94,6 +95,7 @@ public:
audio_io_handle_t output,
pid_t tid,
int *sessionId,
+ String8& name,
status_t *status)
{
Parcel data, reply;
@@ -106,7 +108,12 @@ public:
data.writeInt32(frameCount);
track_flags_t lFlags = flags != NULL ? *flags : (track_flags_t) TRACK_DEFAULT;
data.writeInt32(lFlags);
- data.writeStrongBinder(sharedBuffer->asBinder());
+ if (sharedBuffer != 0) {
+ data.writeInt32(true);
+ data.writeStrongBinder(sharedBuffer->asBinder());
+ } else {
+ data.writeInt32(false);
+ }
data.writeInt32((int32_t) output);
data.writeInt32((int32_t) tid);
int lSessionId = 0;
@@ -126,6 +133,7 @@ public:
if (sessionId != NULL) {
*sessionId = lSessionId;
}
+ name = reply.readString8();
lStatus = reply.readInt32();
track = interface_cast<IAudioTrack>(reply.readStrongBinder());
}
@@ -141,7 +149,7 @@ public:
audio_format_t format,
audio_channel_mask_t channelMask,
size_t frameCount,
- track_flags_t flags,
+ track_flags_t *flags,
pid_t tid,
int *sessionId,
status_t *status)
@@ -154,7 +162,8 @@ public:
data.writeInt32(format);
data.writeInt32(channelMask);
data.writeInt32(frameCount);
- data.writeInt32(flags);
+ track_flags_t lFlags = flags != NULL ? *flags : (track_flags_t) TRACK_DEFAULT;
+ data.writeInt32(lFlags);
data.writeInt32((int32_t) tid);
int lSessionId = 0;
if (sessionId != NULL) {
@@ -165,12 +174,27 @@ public:
if (lStatus != NO_ERROR) {
ALOGE("openRecord error: %s", strerror(-lStatus));
} else {
+ lFlags = reply.readInt32();
+ if (flags != NULL) {
+ *flags = lFlags;
+ }
lSessionId = reply.readInt32();
if (sessionId != NULL) {
*sessionId = lSessionId;
}
lStatus = reply.readInt32();
record = interface_cast<IAudioRecord>(reply.readStrongBinder());
+ if (lStatus == NO_ERROR) {
+ if (record == 0) {
+ ALOGE("openRecord should have returned an IAudioRecord");
+ lStatus = UNKNOWN_ERROR;
+ }
+ } else {
+ if (record != 0) {
+ ALOGE("openRecord returned an IAudioRecord but with status %d", lStatus);
+ record.clear();
+ }
+ }
}
if (status) {
*status = lStatus;
@@ -361,15 +385,16 @@ public:
audio_format_t *pFormat,
audio_channel_mask_t *pChannelMask,
uint32_t *pLatencyMs,
- audio_output_flags_t flags)
+ audio_output_flags_t flags,
+ const audio_offload_info_t *offloadInfo)
{
Parcel data, reply;
- audio_devices_t devices = pDevices ? *pDevices : (audio_devices_t)0;
- uint32_t samplingRate = pSamplingRate ? *pSamplingRate : 0;
- audio_format_t format = pFormat ? *pFormat : AUDIO_FORMAT_DEFAULT;
- audio_channel_mask_t channelMask = pChannelMask ? *pChannelMask : (audio_channel_mask_t)0;
- uint32_t latency = pLatencyMs ? *pLatencyMs : 0;
-
+ audio_devices_t devices = pDevices != NULL ? *pDevices : (audio_devices_t)0;
+ uint32_t samplingRate = pSamplingRate != NULL ? *pSamplingRate : 0;
+ audio_format_t format = pFormat != NULL ? *pFormat : AUDIO_FORMAT_DEFAULT;
+ audio_channel_mask_t channelMask = pChannelMask != NULL ?
+ *pChannelMask : (audio_channel_mask_t)0;
+ uint32_t latency = pLatencyMs != NULL ? *pLatencyMs : 0;
data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
data.writeInt32(module);
data.writeInt32(devices);
@@ -378,19 +403,25 @@ public:
data.writeInt32(channelMask);
data.writeInt32(latency);
data.writeInt32((int32_t) flags);
+ if (offloadInfo == NULL) {
+ data.writeInt32(0);
+ } else {
+ data.writeInt32(1);
+ data.write(offloadInfo, sizeof(audio_offload_info_t));
+ }
remote()->transact(OPEN_OUTPUT, data, &reply);
audio_io_handle_t output = (audio_io_handle_t) reply.readInt32();
ALOGV("openOutput() returned output, %d", output);
devices = (audio_devices_t)reply.readInt32();
- if (pDevices) *pDevices = devices;
+ if (pDevices != NULL) *pDevices = devices;
samplingRate = reply.readInt32();
- if (pSamplingRate) *pSamplingRate = samplingRate;
+ if (pSamplingRate != NULL) *pSamplingRate = samplingRate;
format = (audio_format_t) reply.readInt32();
- if (pFormat) *pFormat = format;
+ if (pFormat != NULL) *pFormat = format;
channelMask = (audio_channel_mask_t)reply.readInt32();
- if (pChannelMask) *pChannelMask = channelMask;
+ if (pChannelMask != NULL) *pChannelMask = channelMask;
latency = reply.readInt32();
- if (pLatencyMs) *pLatencyMs = latency;
+ if (pLatencyMs != NULL) *pLatencyMs = latency;
return output;
}
@@ -439,10 +470,11 @@ public:
audio_channel_mask_t *pChannelMask)
{
Parcel data, reply;
- audio_devices_t devices = pDevices ? *pDevices : (audio_devices_t)0;
- uint32_t samplingRate = pSamplingRate ? *pSamplingRate : 0;
- audio_format_t format = pFormat ? *pFormat : AUDIO_FORMAT_DEFAULT;
- audio_channel_mask_t channelMask = pChannelMask ? *pChannelMask : (audio_channel_mask_t)0;
+ audio_devices_t devices = pDevices != NULL ? *pDevices : (audio_devices_t)0;
+ uint32_t samplingRate = pSamplingRate != NULL ? *pSamplingRate : 0;
+ audio_format_t format = pFormat != NULL ? *pFormat : AUDIO_FORMAT_DEFAULT;
+ audio_channel_mask_t channelMask = pChannelMask != NULL ?
+ *pChannelMask : (audio_channel_mask_t)0;
data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
data.writeInt32(module);
@@ -453,13 +485,13 @@ public:
remote()->transact(OPEN_INPUT, data, &reply);
audio_io_handle_t input = (audio_io_handle_t) reply.readInt32();
devices = (audio_devices_t)reply.readInt32();
- if (pDevices) *pDevices = devices;
+ if (pDevices != NULL) *pDevices = devices;
samplingRate = reply.readInt32();
- if (pSamplingRate) *pSamplingRate = samplingRate;
+ if (pSamplingRate != NULL) *pSamplingRate = samplingRate;
format = (audio_format_t) reply.readInt32();
- if (pFormat) *pFormat = format;
+ if (pFormat != NULL) *pFormat = format;
channelMask = (audio_channel_mask_t)reply.readInt32();
- if (pChannelMask) *pChannelMask = channelMask;
+ if (pChannelMask != NULL) *pChannelMask = channelMask;
return input;
}
@@ -695,6 +727,15 @@ public:
return reply.readInt32();
}
+ virtual status_t setLowRamDevice(bool isLowRamDevice)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
+ data.writeInt32((int) isLowRamDevice);
+ remote()->transact(SET_LOW_RAM_DEVICE, data, &reply);
+ return reply.readInt32();
+ }
+
};
IMPLEMENT_META_INTERFACE(AudioFlinger, "android.media.IAudioFlinger");
@@ -713,16 +754,30 @@ status_t BnAudioFlinger::onTransact(
audio_channel_mask_t channelMask = data.readInt32();
size_t frameCount = data.readInt32();
track_flags_t flags = (track_flags_t) data.readInt32();
- sp<IMemory> buffer = interface_cast<IMemory>(data.readStrongBinder());
+ bool haveSharedBuffer = data.readInt32() != 0;
+ sp<IMemory> buffer;
+ if (haveSharedBuffer) {
+ buffer = interface_cast<IMemory>(data.readStrongBinder());
+ }
audio_io_handle_t output = (audio_io_handle_t) data.readInt32();
pid_t tid = (pid_t) data.readInt32();
int sessionId = data.readInt32();
+ String8 name;
status_t status;
- sp<IAudioTrack> track = createTrack(
- (audio_stream_type_t) streamType, sampleRate, format,
- channelMask, frameCount, &flags, buffer, output, tid, &sessionId, &status);
+ sp<IAudioTrack> track;
+ if ((haveSharedBuffer && (buffer == 0)) ||
+ ((buffer != 0) && (buffer->pointer() == NULL))) {
+ ALOGW("CREATE_TRACK: cannot retrieve shared memory");
+ status = DEAD_OBJECT;
+ } else {
+ track = createTrack(
+ (audio_stream_type_t) streamType, sampleRate, format,
+ channelMask, frameCount, &flags, buffer, output, tid,
+ &sessionId, name, &status);
+ }
reply->writeInt32(flags);
reply->writeInt32(sessionId);
+ reply->writeString8(name);
reply->writeInt32(status);
reply->writeStrongBinder(track->asBinder());
return NO_ERROR;
@@ -739,7 +794,9 @@ status_t BnAudioFlinger::onTransact(
int sessionId = data.readInt32();
status_t status;
sp<IAudioRecord> record = openRecord(input,
- sampleRate, format, channelMask, frameCount, flags, tid, &sessionId, &status);
+ sampleRate, format, channelMask, frameCount, &flags, tid, &sessionId, &status);
+ LOG_ALWAYS_FATAL_IF((record != 0) != (status == NO_ERROR));
+ reply->writeInt32(flags);
reply->writeInt32(sessionId);
reply->writeInt32(status);
reply->writeStrongBinder(record->asBinder());
@@ -868,13 +925,19 @@ status_t BnAudioFlinger::onTransact(
audio_channel_mask_t channelMask = (audio_channel_mask_t)data.readInt32();
uint32_t latency = data.readInt32();
audio_output_flags_t flags = (audio_output_flags_t) data.readInt32();
+ bool hasOffloadInfo = data.readInt32() != 0;
+ audio_offload_info_t offloadInfo;
+ if (hasOffloadInfo) {
+ data.read(&offloadInfo, sizeof(audio_offload_info_t));
+ }
audio_io_handle_t output = openOutput(module,
&devices,
&samplingRate,
&format,
&channelMask,
&latency,
- flags);
+ flags,
+ hasOffloadInfo ? &offloadInfo : NULL);
ALOGV("OPEN_OUTPUT output, %p", output);
reply->writeInt32((int32_t) output);
reply->writeInt32(devices);
@@ -1056,6 +1119,12 @@ status_t BnAudioFlinger::onTransact(
reply->writeInt32(getPrimaryOutputFrameCount());
return NO_ERROR;
} break;
+ case SET_LOW_RAM_DEVICE: {
+ CHECK_INTERFACE(IAudioFlinger, data, reply);
+ bool isLowRamDevice = data.readInt32() != 0;
+ reply->writeInt32(setLowRamDevice(isLowRamDevice));
+ return NO_ERROR;
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/media/libmedia/IAudioFlingerClient.cpp b/media/libmedia/IAudioFlingerClient.cpp
index 2d1e0f8..3c0d4cf 100644
--- a/media/libmedia/IAudioFlingerClient.cpp
+++ b/media/libmedia/IAudioFlingerClient.cpp
@@ -54,7 +54,7 @@ public:
(const AudioSystem::OutputDescriptor *)param2;
data.writeInt32(desc->samplingRate);
data.writeInt32(desc->format);
- data.writeInt32(desc->channels);
+ data.writeInt32(desc->channelMask);
data.writeInt32(desc->frameCount);
data.writeInt32(desc->latency);
}
@@ -83,8 +83,8 @@ status_t BnAudioFlingerClient::onTransact(
ALOGV("STREAM_CONFIG_CHANGED stream %d", stream);
} else if (event != AudioSystem::OUTPUT_CLOSED && event != AudioSystem::INPUT_CLOSED) {
desc.samplingRate = data.readInt32();
- desc.format = data.readInt32();
- desc.channels = data.readInt32();
+ desc.format = (audio_format_t) data.readInt32();
+ desc.channelMask = (audio_channel_mask_t) data.readInt32();
desc.frameCount = data.readInt32();
desc.latency = data.readInt32();
param2 = &desc;
diff --git a/media/libmedia/IAudioPolicyService.cpp b/media/libmedia/IAudioPolicyService.cpp
index 386c351..4be3c09 100644
--- a/media/libmedia/IAudioPolicyService.cpp
+++ b/media/libmedia/IAudioPolicyService.cpp
@@ -56,7 +56,8 @@ enum {
GET_DEVICES_FOR_STREAM,
QUERY_DEFAULT_PRE_PROCESSING,
SET_EFFECT_ENABLED,
- IS_STREAM_ACTIVE_REMOTELY
+ IS_STREAM_ACTIVE_REMOTELY,
+ IS_OFFLOAD_SUPPORTED
};
class BpAudioPolicyService : public BpInterface<IAudioPolicyService>
@@ -126,7 +127,8 @@ public:
uint32_t samplingRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- audio_output_flags_t flags)
+ audio_output_flags_t flags,
+ const audio_offload_info_t *offloadInfo)
{
Parcel data, reply;
data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
@@ -135,6 +137,12 @@ public:
data.writeInt32(static_cast <uint32_t>(format));
data.writeInt32(channelMask);
data.writeInt32(static_cast <uint32_t>(flags));
+ if (offloadInfo == NULL) {
+ data.writeInt32(0);
+ } else {
+ data.writeInt32(1);
+ data.write(offloadInfo, sizeof(audio_offload_info_t));
+ }
remote()->transact(GET_OUTPUT, data, &reply);
return static_cast <audio_io_handle_t> (reply.readInt32());
}
@@ -374,6 +382,14 @@ public:
*count = retCount;
return status;
}
+
+ virtual bool isOffloadSupported(const audio_offload_info_t& info)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
+ data.write(&info, sizeof(audio_offload_info_t));
+ remote()->transact(IS_OFFLOAD_SUPPORTED, data, &reply);
+ return reply.readInt32(); }
};
IMPLEMENT_META_INTERFACE(AudioPolicyService, "android.media.IAudioPolicyService");
@@ -442,12 +458,17 @@ status_t BnAudioPolicyService::onTransact(
audio_channel_mask_t channelMask = data.readInt32();
audio_output_flags_t flags =
static_cast <audio_output_flags_t>(data.readInt32());
-
+ bool hasOffloadInfo = data.readInt32() != 0;
+ audio_offload_info_t offloadInfo;
+ if (hasOffloadInfo) {
+ data.read(&offloadInfo, sizeof(audio_offload_info_t));
+ }
audio_io_handle_t output = getOutput(stream,
samplingRate,
format,
channelMask,
- flags);
+ flags,
+ hasOffloadInfo ? &offloadInfo : NULL);
reply->writeInt32(static_cast <int>(output));
return NO_ERROR;
} break;
@@ -654,6 +675,15 @@ status_t BnAudioPolicyService::onTransact(
return status;
}
+ case IS_OFFLOAD_SUPPORTED: {
+ CHECK_INTERFACE(IAudioPolicyService, data, reply);
+ audio_offload_info_t info;
+ data.read(&info, sizeof(audio_offload_info_t));
+ bool isSupported = isOffloadSupported(info);
+ reply->writeInt32(isSupported);
+ return NO_ERROR;
+ }
+
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/media/libmedia/IAudioRecord.cpp b/media/libmedia/IAudioRecord.cpp
index 0d06e98..4a7de65 100644
--- a/media/libmedia/IAudioRecord.cpp
+++ b/media/libmedia/IAudioRecord.cpp
@@ -42,6 +42,18 @@ public:
{
}
+ virtual sp<IMemory> getCblk() const
+ {
+ Parcel data, reply;
+ sp<IMemory> cblk;
+ data.writeInterfaceToken(IAudioRecord::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_CBLK, data, &reply);
+ if (status == NO_ERROR) {
+ cblk = interface_cast<IMemory>(reply.readStrongBinder());
+ }
+ return cblk;
+ }
+
virtual status_t start(int /*AudioSystem::sync_event_t*/ event, int triggerSession)
{
Parcel data, reply;
@@ -64,17 +76,6 @@ public:
remote()->transact(STOP, data, &reply);
}
- virtual sp<IMemory> getCblk() const
- {
- Parcel data, reply;
- sp<IMemory> cblk;
- data.writeInterfaceToken(IAudioRecord::getInterfaceDescriptor());
- status_t status = remote()->transact(GET_CBLK, data, &reply);
- if (status == NO_ERROR) {
- cblk = interface_cast<IMemory>(reply.readStrongBinder());
- }
- return cblk;
- }
};
IMPLEMENT_META_INTERFACE(AudioRecord, "android.media.IAudioRecord");
diff --git a/media/libmedia/IAudioTrack.cpp b/media/libmedia/IAudioTrack.cpp
index e92f8aa..3cd9cfd 100644
--- a/media/libmedia/IAudioTrack.cpp
+++ b/media/libmedia/IAudioTrack.cpp
@@ -39,6 +39,9 @@ enum {
ALLOCATE_TIMED_BUFFER,
QUEUE_TIMED_BUFFER,
SET_MEDIA_TIME_TRANSFORM,
+ SET_PARAMETERS,
+ GET_TIMESTAMP,
+ SIGNAL,
};
class BpAudioTrack : public BpInterface<IAudioTrack>
@@ -154,6 +157,38 @@ public:
}
return status;
}
+
+ virtual status_t setParameters(const String8& keyValuePairs) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeString8(keyValuePairs);
+ status_t status = remote()->transact(SET_PARAMETERS, data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ }
+ return status;
+ }
+
+ virtual status_t getTimestamp(AudioTimestamp& timestamp) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_TIMESTAMP, data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ if (status == NO_ERROR) {
+ timestamp.mPosition = reply.readInt32();
+ timestamp.mTime.tv_sec = reply.readInt32();
+ timestamp.mTime.tv_nsec = reply.readInt32();
+ }
+ }
+ return status;
+ }
+
+ virtual void signal() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ remote()->transact(SIGNAL, data, &reply);
+ }
};
IMPLEMENT_META_INTERFACE(AudioTrack, "android.media.IAudioTrack");
@@ -223,6 +258,29 @@ status_t BnAudioTrack::onTransact(
reply->writeInt32(setMediaTimeTransform(xform, target));
return NO_ERROR;
} break;
+ case SET_PARAMETERS: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ String8 keyValuePairs(data.readString8());
+ reply->writeInt32(setParameters(keyValuePairs));
+ return NO_ERROR;
+ } break;
+ case GET_TIMESTAMP: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ AudioTimestamp timestamp;
+ status_t status = getTimestamp(timestamp);
+ reply->writeInt32(status);
+ if (status == NO_ERROR) {
+ reply->writeInt32(timestamp.mPosition);
+ reply->writeInt32(timestamp.mTime.tv_sec);
+ reply->writeInt32(timestamp.mTime.tv_nsec);
+ }
+ return NO_ERROR;
+ } break;
+ case SIGNAL: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ signal();
+ return NO_ERROR;
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/media/libmedia/IDrm.cpp b/media/libmedia/IDrm.cpp
index 902aeb2..f7a9a75 100644
--- a/media/libmedia/IDrm.cpp
+++ b/media/libmedia/IDrm.cpp
@@ -68,10 +68,11 @@ struct BpDrm : public BpInterface<IDrm> {
return reply.readInt32();
}
- virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) {
+ virtual bool isCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) {
Parcel data, reply;
data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
data.write(uuid, 16);
+ data.writeString8(mimeType);
remote()->transact(IS_CRYPTO_SUPPORTED, data, &reply);
return reply.readInt32() != 0;
@@ -438,7 +439,9 @@ status_t BnDrm::onTransact(
CHECK_INTERFACE(IDrm, data, reply);
uint8_t uuid[16];
data.read(uuid, sizeof(uuid));
- reply->writeInt32(isCryptoSchemeSupported(uuid));
+ String8 mimeType = data.readString8();
+ reply->writeInt32(isCryptoSchemeSupported(uuid, mimeType));
+
return OK;
}
diff --git a/media/libmedia/IHDCP.cpp b/media/libmedia/IHDCP.cpp
index f13addc..1cf987a 100644
--- a/media/libmedia/IHDCP.cpp
+++ b/media/libmedia/IHDCP.cpp
@@ -30,7 +30,9 @@ enum {
HDCP_SET_OBSERVER,
HDCP_INIT_ASYNC,
HDCP_SHUTDOWN_ASYNC,
+ HDCP_GET_CAPS,
HDCP_ENCRYPT,
+ HDCP_ENCRYPT_NATIVE,
HDCP_DECRYPT,
};
@@ -84,6 +86,13 @@ struct BpHDCP : public BpInterface<IHDCP> {
return reply.readInt32();
}
+ virtual uint32_t getCaps() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ remote()->transact(HDCP_GET_CAPS, data, &reply);
+ return reply.readInt32();
+ }
+
virtual status_t encrypt(
const void *inData, size_t size, uint32_t streamCTR,
uint64_t *outInputCTR, void *outData) {
@@ -108,6 +117,31 @@ struct BpHDCP : public BpInterface<IHDCP> {
return err;
}
+ virtual status_t encryptNative(
+ const sp<GraphicBuffer> &graphicBuffer,
+ size_t offset, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ data.write(*graphicBuffer);
+ data.writeInt32(offset);
+ data.writeInt32(size);
+ data.writeInt32(streamCTR);
+ remote()->transact(HDCP_ENCRYPT_NATIVE, data, &reply);
+
+ status_t err = reply.readInt32();
+
+ if (err != OK) {
+ *outInputCTR = 0;
+ return err;
+ }
+
+ *outInputCTR = reply.readInt64();
+ reply.read(outData, size);
+
+ return err;
+ }
+
virtual status_t decrypt(
const void *inData, size_t size,
uint32_t streamCTR, uint64_t inputCTR,
@@ -196,6 +230,14 @@ status_t BnHDCP::onTransact(
return OK;
}
+ case HDCP_GET_CAPS:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ reply->writeInt32(getCaps());
+ return OK;
+ }
+
case HDCP_ENCRYPT:
{
size_t size = data.readInt32();
@@ -222,6 +264,34 @@ status_t BnHDCP::onTransact(
return OK;
}
+ case HDCP_ENCRYPT_NATIVE:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ sp<GraphicBuffer> graphicBuffer = new GraphicBuffer();
+ data.read(*graphicBuffer);
+ size_t offset = data.readInt32();
+ size_t size = data.readInt32();
+ uint32_t streamCTR = data.readInt32();
+ void *outData = malloc(size);
+ uint64_t inputCTR;
+
+ status_t err = encryptNative(graphicBuffer, offset, size,
+ streamCTR, &inputCTR, outData);
+
+ reply->writeInt32(err);
+
+ if (err == OK) {
+ reply->writeInt64(inputCTR);
+ reply->write(outData, size);
+ }
+
+ free(outData);
+ outData = NULL;
+
+ return OK;
+ }
+
case HDCP_DECRYPT:
{
size_t size = data.readInt32();
diff --git a/media/libmedia/IMediaDeathNotifier.cpp b/media/libmedia/IMediaDeathNotifier.cpp
index 9199db6..9db5b1b 100644
--- a/media/libmedia/IMediaDeathNotifier.cpp
+++ b/media/libmedia/IMediaDeathNotifier.cpp
@@ -49,10 +49,10 @@ IMediaDeathNotifier::getMediaPlayerService()
} while (true);
if (sDeathNotifier == NULL) {
- sDeathNotifier = new DeathNotifier();
- }
- binder->linkToDeath(sDeathNotifier);
- sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
+ sDeathNotifier = new DeathNotifier();
+ }
+ binder->linkToDeath(sDeathNotifier);
+ sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
}
ALOGE_IF(sMediaPlayerService == 0, "no media player service!?");
return sMediaPlayerService;
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index 74f574d..3c22b4c 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -86,30 +86,48 @@ public:
return interface_cast<IMediaRecorder>(reply.readStrongBinder());
}
- virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat)
+ virtual status_t decode(const char* url, uint32_t *pSampleRate, int* pNumChannels,
+ audio_format_t* pFormat,
+ const sp<IMemoryHeap>& heap, size_t *pSize)
{
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
data.writeCString(url);
- remote()->transact(DECODE_URL, data, &reply);
- *pSampleRate = uint32_t(reply.readInt32());
- *pNumChannels = reply.readInt32();
- *pFormat = (audio_format_t) reply.readInt32();
- return interface_cast<IMemory>(reply.readStrongBinder());
+ data.writeStrongBinder(heap->asBinder());
+ status_t status = remote()->transact(DECODE_URL, data, &reply);
+ if (status == NO_ERROR) {
+ status = (status_t)reply.readInt32();
+ if (status == NO_ERROR) {
+ *pSampleRate = uint32_t(reply.readInt32());
+ *pNumChannels = reply.readInt32();
+ *pFormat = (audio_format_t)reply.readInt32();
+ *pSize = (size_t)reply.readInt32();
+ }
+ }
+ return status;
}
- virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat)
+ virtual status_t decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate,
+ int* pNumChannels, audio_format_t* pFormat,
+ const sp<IMemoryHeap>& heap, size_t *pSize)
{
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
data.writeFileDescriptor(fd);
data.writeInt64(offset);
data.writeInt64(length);
- remote()->transact(DECODE_FD, data, &reply);
- *pSampleRate = uint32_t(reply.readInt32());
- *pNumChannels = reply.readInt32();
- *pFormat = (audio_format_t) reply.readInt32();
- return interface_cast<IMemory>(reply.readStrongBinder());
+ data.writeStrongBinder(heap->asBinder());
+ status_t status = remote()->transact(DECODE_FD, data, &reply);
+ if (status == NO_ERROR) {
+ status = (status_t)reply.readInt32();
+ if (status == NO_ERROR) {
+ *pSampleRate = uint32_t(reply.readInt32());
+ *pNumChannels = reply.readInt32();
+ *pFormat = (audio_format_t)reply.readInt32();
+ *pSize = (size_t)reply.readInt32();
+ }
+ }
+ return status;
}
virtual sp<IOMX> getOMX() {
@@ -205,14 +223,19 @@ status_t BnMediaPlayerService::onTransact(
case DECODE_URL: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
const char* url = data.readCString();
+ sp<IMemoryHeap> heap = interface_cast<IMemoryHeap>(data.readStrongBinder());
uint32_t sampleRate;
int numChannels;
audio_format_t format;
- sp<IMemory> player = decode(url, &sampleRate, &numChannels, &format);
- reply->writeInt32(sampleRate);
- reply->writeInt32(numChannels);
- reply->writeInt32((int32_t) format);
- reply->writeStrongBinder(player->asBinder());
+ size_t size;
+ status_t status = decode(url, &sampleRate, &numChannels, &format, heap, &size);
+ reply->writeInt32(status);
+ if (status == NO_ERROR) {
+ reply->writeInt32(sampleRate);
+ reply->writeInt32(numChannels);
+ reply->writeInt32((int32_t)format);
+ reply->writeInt32((int32_t)size);
+ }
return NO_ERROR;
} break;
case DECODE_FD: {
@@ -220,14 +243,20 @@ status_t BnMediaPlayerService::onTransact(
int fd = dup(data.readFileDescriptor());
int64_t offset = data.readInt64();
int64_t length = data.readInt64();
+ sp<IMemoryHeap> heap = interface_cast<IMemoryHeap>(data.readStrongBinder());
uint32_t sampleRate;
int numChannels;
audio_format_t format;
- sp<IMemory> player = decode(fd, offset, length, &sampleRate, &numChannels, &format);
- reply->writeInt32(sampleRate);
- reply->writeInt32(numChannels);
- reply->writeInt32((int32_t) format);
- reply->writeStrongBinder(player->asBinder());
+ size_t size;
+ status_t status = decode(fd, offset, length, &sampleRate, &numChannels, &format,
+ heap, &size);
+ reply->writeInt32(status);
+ if (status == NO_ERROR) {
+ reply->writeInt32(sampleRate);
+ reply->writeInt32(numChannels);
+ reply->writeInt32((int32_t)format);
+ reply->writeInt32((int32_t)size);
+ }
return NO_ERROR;
} break;
case CREATE_MEDIA_RECORDER: {
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index d6cd43a..71ce320 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -43,6 +43,7 @@ enum {
CREATE_INPUT_SURFACE,
SIGNAL_END_OF_INPUT_STREAM,
STORE_META_DATA_IN_BUFFERS,
+ PREPARE_FOR_ADAPTIVE_PLAYBACK,
ALLOC_BUFFER,
ALLOC_BUFFER_WITH_BACKUP,
FREE_BUFFER,
@@ -51,6 +52,8 @@ enum {
GET_EXTENSION_INDEX,
OBSERVER_ON_MSG,
GET_GRAPHIC_BUFFER_USAGE,
+ SET_INTERNAL_OPTION,
+ UPDATE_GRAPHIC_BUFFER_IN_META,
};
class BpOMX : public BpInterface<IOMX> {
@@ -282,6 +285,21 @@ public:
return err;
}
+ virtual status_t updateGraphicBufferInMeta(
+ node_id node, OMX_U32 port_index,
+ const sp<GraphicBuffer> &graphicBuffer, buffer_id buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ data.writeIntPtr((intptr_t)node);
+ data.writeInt32(port_index);
+ data.write(*graphicBuffer);
+ data.writeIntPtr((intptr_t)buffer);
+ remote()->transact(UPDATE_GRAPHIC_BUFFER_IN_META, data, &reply);
+
+ status_t err = reply.readInt32();
+ return err;
+ }
+
virtual status_t createInputSurface(
node_id node, OMX_U32 port_index,
sp<IGraphicBufferProducer> *bufferProducer) {
@@ -334,6 +352,22 @@ public:
return err;
}
+ virtual status_t prepareForAdaptivePlayback(
+ node_id node, OMX_U32 port_index, OMX_BOOL enable,
+ OMX_U32 max_width, OMX_U32 max_height) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ data.writeIntPtr((intptr_t)node);
+ data.writeInt32(port_index);
+ data.writeInt32((int32_t)enable);
+ data.writeInt32(max_width);
+ data.writeInt32(max_height);
+ remote()->transact(PREPARE_FOR_ADAPTIVE_PLAYBACK, data, &reply);
+
+ status_t err = reply.readInt32();
+ return err;
+ }
+
virtual status_t allocateBuffer(
node_id node, OMX_U32 port_index, size_t size,
buffer_id *buffer, void **buffer_data) {
@@ -439,6 +473,24 @@ public:
return err;
}
+
+ virtual status_t setInternalOption(
+ node_id node,
+ OMX_U32 port_index,
+ InternalOptionType type,
+ const void *optionData,
+ size_t size) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ data.writeIntPtr((intptr_t)node);
+ data.writeInt32(port_index);
+ data.writeInt32(size);
+ data.write(optionData, size);
+ data.writeInt32(type);
+ remote()->transact(SET_INTERNAL_OPTION, data, &reply);
+
+ return reply.readInt32();
+ }
};
IMPLEMENT_META_INTERFACE(OMX, "android.hardware.IOMX");
@@ -537,6 +589,7 @@ status_t BnOMX::onTransact(
case SET_PARAMETER:
case GET_CONFIG:
case SET_CONFIG:
+ case SET_INTERNAL_OPTION:
{
CHECK_OMX_INTERFACE(IOMX, data, reply);
@@ -562,6 +615,15 @@ status_t BnOMX::onTransact(
case SET_CONFIG:
err = setConfig(node, index, params, size);
break;
+ case SET_INTERNAL_OPTION:
+ {
+ InternalOptionType type =
+ (InternalOptionType)data.readInt32();
+
+ err = setInternalOption(node, index, type, params, size);
+ break;
+ }
+
default:
TRESPASS();
}
@@ -662,6 +724,23 @@ status_t BnOMX::onTransact(
return NO_ERROR;
}
+ case UPDATE_GRAPHIC_BUFFER_IN_META:
+ {
+ CHECK_OMX_INTERFACE(IOMX, data, reply);
+
+ node_id node = (void*)data.readIntPtr();
+ OMX_U32 port_index = data.readInt32();
+ sp<GraphicBuffer> graphicBuffer = new GraphicBuffer();
+ data.read(*graphicBuffer);
+ buffer_id buffer = (void*)data.readIntPtr();
+
+ status_t err = updateGraphicBufferInMeta(
+ node, port_index, graphicBuffer, buffer);
+ reply->writeInt32(err);
+
+ return NO_ERROR;
+ }
+
case CREATE_INPUT_SURFACE:
{
CHECK_OMX_INTERFACE(IOMX, data, reply);
@@ -708,6 +787,23 @@ status_t BnOMX::onTransact(
return NO_ERROR;
}
+ case PREPARE_FOR_ADAPTIVE_PLAYBACK:
+ {
+ CHECK_OMX_INTERFACE(IOMX, data, reply);
+
+ node_id node = (void*)data.readIntPtr();
+ OMX_U32 port_index = data.readInt32();
+ OMX_BOOL enable = (OMX_BOOL)data.readInt32();
+ OMX_U32 max_width = data.readInt32();
+ OMX_U32 max_height = data.readInt32();
+
+ status_t err = prepareForAdaptivePlayback(
+ node, port_index, enable, max_width, max_height);
+ reply->writeInt32(err);
+
+ return NO_ERROR;
+ }
+
case ALLOC_BUFFER:
{
CHECK_OMX_INTERFACE(IOMX, data, reply);
diff --git a/media/libmedia/IRemoteDisplayClient.cpp b/media/libmedia/IRemoteDisplayClient.cpp
index 5c494b3..7190879 100644
--- a/media/libmedia/IRemoteDisplayClient.cpp
+++ b/media/libmedia/IRemoteDisplayClient.cpp
@@ -38,7 +38,7 @@ public:
}
void onDisplayConnected(const sp<IGraphicBufferProducer>& bufferProducer,
- uint32_t width, uint32_t height, uint32_t flags)
+ uint32_t width, uint32_t height, uint32_t flags, uint32_t session)
{
Parcel data, reply;
data.writeInterfaceToken(IRemoteDisplayClient::getInterfaceDescriptor());
@@ -46,6 +46,7 @@ public:
data.writeInt32(width);
data.writeInt32(height);
data.writeInt32(flags);
+ data.writeInt32(session);
remote()->transact(ON_DISPLAY_CONNECTED, data, &reply, IBinder::FLAG_ONEWAY);
}
@@ -80,7 +81,8 @@ status_t BnRemoteDisplayClient::onTransact(
uint32_t width = data.readInt32();
uint32_t height = data.readInt32();
uint32_t flags = data.readInt32();
- onDisplayConnected(surfaceTexture, width, height, flags);
+ uint32_t session = data.readInt32();
+ onDisplayConnected(surfaceTexture, width, height, flags, session);
return NO_ERROR;
}
case ON_DISPLAY_DISCONNECTED: {
diff --git a/media/libmedia/JetPlayer.cpp b/media/libmedia/JetPlayer.cpp
index 59e538f..e914b34 100644
--- a/media/libmedia/JetPlayer.cpp
+++ b/media/libmedia/JetPlayer.cpp
@@ -18,8 +18,6 @@
#define LOG_TAG "JetPlayer-C"
#include <utils/Log.h>
-#include <utils/threads.h>
-
#include <media/JetPlayer.h>
@@ -39,7 +37,6 @@ JetPlayer::JetPlayer(void *javaJetPlayer, int maxTracks, int trackBufferSize) :
mMaxTracks(maxTracks),
mEasData(NULL),
mEasJetFileLoc(NULL),
- mAudioTrack(NULL),
mTrackBufferSize(trackBufferSize)
{
ALOGV("JetPlayer constructor");
@@ -140,11 +137,10 @@ int JetPlayer::release()
free(mEasJetFileLoc);
mEasJetFileLoc = NULL;
}
- if (mAudioTrack) {
+ if (mAudioTrack != 0) {
mAudioTrack->stop();
mAudioTrack->flush();
- delete mAudioTrack;
- mAudioTrack = NULL;
+ mAudioTrack.clear();
}
if (mAudioBuffer) {
delete mAudioBuffer;
diff --git a/media/libmedia/MediaScannerClient.cpp b/media/libmedia/MediaScannerClient.cpp
index e1e3348..93a4a4c 100644
--- a/media/libmedia/MediaScannerClient.cpp
+++ b/media/libmedia/MediaScannerClient.cpp
@@ -16,7 +16,7 @@
#include <media/mediascanner.h>
-#include <utils/StringArray.h>
+#include "StringArray.h"
#include "autodetect.h"
#include "unicode/ucnv.h"
diff --git a/media/libmedia/SingleStateQueueInstantiations.cpp b/media/libmedia/SingleStateQueueInstantiations.cpp
index 2afebe9..0265c8c 100644
--- a/media/libmedia/SingleStateQueueInstantiations.cpp
+++ b/media/libmedia/SingleStateQueueInstantiations.cpp
@@ -16,11 +16,13 @@
#include <media/SingleStateQueue.h>
#include <private/media/StaticAudioTrackState.h>
+#include <media/AudioTimestamp.h>
// FIXME hack for gcc
namespace android {
template class SingleStateQueue<StaticAudioTrackState>; // typedef StaticAudioTrackSingleStateQueue
+template class SingleStateQueue<AudioTimestamp>; // typedef AudioTimestampSingleStateQueue
}
diff --git a/media/libmedia/SoundPool.cpp b/media/libmedia/SoundPool.cpp
index ee70ef7..22e9fad 100644
--- a/media/libmedia/SoundPool.cpp
+++ b/media/libmedia/SoundPool.cpp
@@ -18,16 +18,10 @@
#define LOG_TAG "SoundPool"
#include <utils/Log.h>
-//#define USE_SHARED_MEM_BUFFER
-
-// XXX needed for timing latency
-#include <utils/Timers.h>
+#define USE_SHARED_MEM_BUFFER
#include <media/AudioTrack.h>
#include <media/mediaplayer.h>
-
-#include <system/audio.h>
-
#include <media/SoundPool.h>
#include "SoundPoolThread.h"
@@ -38,6 +32,8 @@ int kDefaultBufferCount = 4;
uint32_t kMaxSampleRate = 48000;
uint32_t kDefaultSampleRate = 44100;
uint32_t kDefaultFrameCount = 1200;
+size_t kDefaultHeapSize = 1024 * 1024; // 1MB
+
SoundPool::SoundPool(int maxChannels, audio_stream_type_t streamType, int srcQuality)
{
@@ -470,7 +466,6 @@ Sample::Sample(int sampleID, int fd, int64_t offset, int64_t length)
void Sample::init()
{
- mData = 0;
mSize = 0;
mRefCount = 0;
mSampleID = 0;
@@ -488,7 +483,6 @@ Sample::~Sample()
ALOGV("close(%d)", mFd);
::close(mFd);
}
- mData.clear();
free(mUrl);
}
@@ -497,44 +491,48 @@ status_t Sample::doLoad()
uint32_t sampleRate;
int numChannels;
audio_format_t format;
- sp<IMemory> p;
+ status_t status;
+ mHeap = new MemoryHeapBase(kDefaultHeapSize);
+
ALOGV("Start decode");
if (mUrl) {
- p = MediaPlayer::decode(mUrl, &sampleRate, &numChannels, &format);
+ status = MediaPlayer::decode(mUrl, &sampleRate, &numChannels, &format, mHeap, &mSize);
} else {
- p = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format);
+ status = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
+ mHeap, &mSize);
ALOGV("close(%d)", mFd);
::close(mFd);
mFd = -1;
}
- if (p == 0) {
+ if (status != NO_ERROR) {
ALOGE("Unable to load sample: %s", mUrl);
- return -1;
+ goto error;
}
ALOGV("pointer = %p, size = %u, sampleRate = %u, numChannels = %d",
- p->pointer(), p->size(), sampleRate, numChannels);
+ mHeap->getBase(), mSize, sampleRate, numChannels);
if (sampleRate > kMaxSampleRate) {
ALOGE("Sample rate (%u) out of range", sampleRate);
- return - 1;
+ status = BAD_VALUE;
+ goto error;
}
if ((numChannels < 1) || (numChannels > 2)) {
ALOGE("Sample channel count (%d) out of range", numChannels);
- return - 1;
+ status = BAD_VALUE;
+ goto error;
}
- //_dumpBuffer(p->pointer(), p->size());
- uint8_t* q = static_cast<uint8_t*>(p->pointer()) + p->size() - 10;
- //_dumpBuffer(q, 10, 10, false);
-
- mData = p;
- mSize = p->size();
+ mData = new MemoryBase(mHeap, 0, mSize);
mSampleRate = sampleRate;
mNumChannels = numChannels;
mFormat = format;
mState = READY;
- return 0;
+ return NO_ERROR;
+
+error:
+ mHeap.clear();
+ return status;
}
@@ -547,8 +545,8 @@ void SoundChannel::init(SoundPool* soundPool)
void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,
float rightVolume, int priority, int loop, float rate)
{
- AudioTrack* oldTrack;
- AudioTrack* newTrack;
+ sp<AudioTrack> oldTrack;
+ sp<AudioTrack> newTrack;
status_t status;
{ // scope for the lock
@@ -608,7 +606,7 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV
// do not create a new audio track if current track is compatible with sample parameters
#ifdef USE_SHARED_MEM_BUFFER
newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channels, sample->getIMemory(), AUDIO_OUTPUT_FLAG_NONE, callback, userData);
+ channels, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
#else
newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
channels, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
@@ -620,7 +618,7 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV
ALOGE("Error creating AudioTrack");
goto exit;
}
- ALOGV("setVolume %p", newTrack);
+ ALOGV("setVolume %p", newTrack.get());
newTrack->setVolume(leftVolume, rightVolume);
newTrack->setLoop(0, frameCount, loop);
@@ -643,11 +641,9 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV
}
exit:
- ALOGV("delete oldTrack %p", oldTrack);
- delete oldTrack;
+ ALOGV("delete oldTrack %p", oldTrack.get());
if (status != NO_ERROR) {
- delete newTrack;
- mAudioTrack = NULL;
+ mAudioTrack.clear();
}
}
@@ -748,11 +744,16 @@ void SoundChannel::process(int event, void *info, unsigned long toggle)
b->size = count;
//ALOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]);
}
- } else if (event == AudioTrack::EVENT_UNDERRUN) {
- ALOGV("process %p channel %d EVENT_UNDERRUN", this, mChannelID);
+ } else if (event == AudioTrack::EVENT_UNDERRUN || event == AudioTrack::EVENT_BUFFER_END ||
+ event == AudioTrack::EVENT_NEW_IAUDIOTRACK) {
+ ALOGV("process %p channel %d event %s",
+ this, mChannelID, (event == AudioTrack::EVENT_UNDERRUN) ? "UNDERRUN" :
+ (event == AudioTrack::EVENT_BUFFER_END) ? "BUFFER_END" : "NEW_IAUDIOTRACK");
mSoundPool->addToStopList(this);
} else if (event == AudioTrack::EVENT_LOOP_END) {
- ALOGV("End loop %p channel %d count %d", this, mChannelID, *(int *)info);
+ ALOGV("End loop %p channel %d", this, mChannelID);
+ } else {
+ ALOGW("SoundChannel::process unexpected event %d", event);
}
}
@@ -884,7 +885,7 @@ SoundChannel::~SoundChannel()
}
// do not call AudioTrack destructor with mLock held as it will wait for the AudioTrack
// callback thread to exit which may need to execute process() and acquire the mLock.
- delete mAudioTrack;
+ mAudioTrack.clear();
}
void SoundChannel::dump()
diff --git a/media/libmedia/StringArray.cpp b/media/libmedia/StringArray.cpp
new file mode 100644
index 0000000..5f5b57a
--- /dev/null
+++ b/media/libmedia/StringArray.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+//
+// Sortable array of strings. STL-ish, but STL-free.
+//
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "StringArray.h"
+
+namespace android {
+
+//
+// An expanding array of strings. Add, get, sort, delete.
+//
+StringArray::StringArray()
+ : mMax(0), mCurrent(0), mArray(NULL)
+{
+}
+
+StringArray:: ~StringArray() {
+ for (int i = 0; i < mCurrent; i++)
+ delete[] mArray[i];
+ delete[] mArray;
+}
+
+//
+// Add a string. A copy of the string is made.
+//
+bool StringArray::push_back(const char* str) {
+ if (mCurrent >= mMax) {
+ char** tmp;
+
+ if (mMax == 0)
+ mMax = 16; // initial storage
+ else
+ mMax *= 2;
+
+ tmp = new char*[mMax];
+ if (tmp == NULL)
+ return false;
+
+ memcpy(tmp, mArray, mCurrent * sizeof(char*));
+ delete[] mArray;
+ mArray = tmp;
+ }
+
+ int len = strlen(str);
+ mArray[mCurrent] = new char[len+1];
+ memcpy(mArray[mCurrent], str, len+1);
+ mCurrent++;
+
+ return true;
+}
+
+//
+// Delete an entry.
+//
+void StringArray::erase(int idx) {
+ if (idx < 0 || idx >= mCurrent)
+ return;
+ delete[] mArray[idx];
+ if (idx < mCurrent-1) {
+ memmove(&mArray[idx], &mArray[idx+1],
+ (mCurrent-1 - idx) * sizeof(char*));
+ }
+ mCurrent--;
+}
+
+//
+// Sort the array.
+//
+void StringArray::sort(int (*compare)(const void*, const void*)) {
+ qsort(mArray, mCurrent, sizeof(char*), compare);
+}
+
+//
+// Pass this to the sort routine to do an ascending alphabetical sort.
+//
+int StringArray::cmpAscendingAlpha(const void* pstr1, const void* pstr2) {
+ return strcmp(*(const char**)pstr1, *(const char**)pstr2);
+}
+
+//
+// Set entry N to specified string.
+// [should use operator[] here]
+//
+void StringArray::setEntry(int idx, const char* str) {
+ if (idx < 0 || idx >= mCurrent)
+ return;
+ delete[] mArray[idx];
+ int len = strlen(str);
+ mArray[idx] = new char[len+1];
+ memcpy(mArray[idx], str, len+1);
+}
+
+
+}; // namespace android
diff --git a/media/libmedia/StringArray.h b/media/libmedia/StringArray.h
new file mode 100644
index 0000000..ae47085
--- /dev/null
+++ b/media/libmedia/StringArray.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+//
+// Sortable array of strings. STL-ish, but STL-free.
+//
+#ifndef _LIBS_MEDIA_STRING_ARRAY_H
+#define _LIBS_MEDIA_STRING_ARRAY_H
+
+#include <stdlib.h>
+#include <string.h>
+
+namespace android {
+
+//
+// An expanding array of strings. Add, get, sort, delete.
+//
+class StringArray {
+public:
+ StringArray();
+ virtual ~StringArray();
+
+ //
+ // Add a string. A copy of the string is made.
+ //
+ bool push_back(const char* str);
+
+ //
+ // Delete an entry.
+ //
+ void erase(int idx);
+
+ //
+ // Sort the array.
+ //
+ void sort(int (*compare)(const void*, const void*));
+
+ //
+ // Pass this to the sort routine to do an ascending alphabetical sort.
+ //
+ static int cmpAscendingAlpha(const void* pstr1, const void* pstr2);
+
+ //
+ // Get the #of items in the array.
+ //
+ inline int size(void) const { return mCurrent; }
+
+ //
+ // Return entry N.
+ // [should use operator[] here]
+ //
+ const char* getEntry(int idx) const {
+ return (unsigned(idx) >= unsigned(mCurrent)) ? NULL : mArray[idx];
+ }
+
+ //
+ // Set entry N to specified string.
+ // [should use operator[] here]
+ //
+ void setEntry(int idx, const char* str);
+
+private:
+ int mMax;
+ int mCurrent;
+ char** mArray;
+};
+
+}; // namespace android
+
+#endif // _LIBS_MEDIA_STRING_ARRAY_H
diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp
index f55b697..adef3be 100644
--- a/media/libmedia/ToneGenerator.cpp
+++ b/media/libmedia/ToneGenerator.cpp
@@ -16,13 +16,9 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "ToneGenerator"
-#include <utils/threads.h>
-#include <stdio.h>
#include <math.h>
#include <utils/Log.h>
-#include <utils/RefBase.h>
-#include <utils/Timers.h>
#include <cutils/properties.h>
#include "media/ToneGenerator.h"
@@ -803,7 +799,6 @@ ToneGenerator::ToneGenerator(audio_stream_type_t streamType, float volume, bool
ALOGV("ToneGenerator constructor: streamType=%d, volume=%f", streamType, volume);
mState = TONE_IDLE;
- mpAudioTrack = NULL;
if (AudioSystem::getOutputSamplingRate(&mSamplingRate, streamType) != NO_ERROR) {
ALOGE("Unable to marshal AudioFlinger");
@@ -855,10 +850,10 @@ ToneGenerator::ToneGenerator(audio_stream_type_t streamType, float volume, bool
ToneGenerator::~ToneGenerator() {
ALOGV("ToneGenerator destructor");
- if (mpAudioTrack != NULL) {
+ if (mpAudioTrack != 0) {
stopTone();
- ALOGV("Delete Track: %p", mpAudioTrack);
- delete mpAudioTrack;
+ ALOGV("Delete Track: %p", mpAudioTrack.get());
+ mpAudioTrack.clear();
}
}
@@ -1047,14 +1042,9 @@ void ToneGenerator::stopTone() {
////////////////////////////////////////////////////////////////////////////////
bool ToneGenerator::initAudioTrack() {
- if (mpAudioTrack) {
- delete mpAudioTrack;
- mpAudioTrack = NULL;
- }
-
// Open audio track in mono, PCM 16bit, default sampling rate, default buffer size
mpAudioTrack = new AudioTrack();
- ALOGV("Create Track: %p", mpAudioTrack);
+ ALOGV("Create Track: %p", mpAudioTrack.get());
mpAudioTrack->set(mStreamType,
0, // sampleRate
@@ -1066,7 +1056,9 @@ bool ToneGenerator::initAudioTrack() {
this, // user
0, // notificationFrames
0, // sharedBuffer
- mThreadCanCallJava);
+ mThreadCanCallJava,
+ 0, // sessionId
+ AudioTrack::TRANSFER_CALLBACK);
if (mpAudioTrack->initCheck() != NO_ERROR) {
ALOGE("AudioTrack->initCheck failed");
@@ -1081,12 +1073,10 @@ bool ToneGenerator::initAudioTrack() {
initAudioTrack_exit:
+ ALOGV("Init failed: %p", mpAudioTrack.get());
+
// Cleanup
- if (mpAudioTrack != NULL) {
- ALOGV("Delete Track I: %p", mpAudioTrack);
- delete mpAudioTrack;
- mpAudioTrack = NULL;
- }
+ mpAudioTrack.clear();
return false;
}
diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp
index 5b4071b..c146b8d 100644
--- a/media/libmedia/Visualizer.cpp
+++ b/media/libmedia/Visualizer.cpp
@@ -28,6 +28,7 @@
#include <media/Visualizer.h>
#include <audio_utils/fixedfft.h>
+#include <utils/Thread.h>
namespace android {
@@ -42,6 +43,7 @@ Visualizer::Visualizer (int32_t priority,
mCaptureSize(CAPTURE_SIZE_DEF),
mSampleRate(44100000),
mScalingMode(VISUALIZER_SCALING_MODE_NORMALIZED),
+ mMeasurementMode(MEASUREMENT_MODE_NONE),
mCaptureCallBack(NULL),
mCaptureCbkUser(NULL)
{
@@ -185,6 +187,73 @@ status_t Visualizer::setScalingMode(uint32_t mode) {
return status;
}
+status_t Visualizer::setMeasurementMode(uint32_t mode) {
+ if ((mode != MEASUREMENT_MODE_NONE)
+ //Note: needs to be handled as a mask when more measurement modes are added
+ && ((mode & MEASUREMENT_MODE_PEAK_RMS) != mode)) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock _l(mCaptureLock);
+
+ uint32_t buf32[sizeof(effect_param_t) / sizeof(uint32_t) + 2];
+ effect_param_t *p = (effect_param_t *)buf32;
+
+ p->psize = sizeof(uint32_t);
+ p->vsize = sizeof(uint32_t);
+ *(int32_t *)p->data = VISUALIZER_PARAM_MEASUREMENT_MODE;
+ *((int32_t *)p->data + 1)= mode;
+ status_t status = setParameter(p);
+
+ ALOGV("setMeasurementMode mode %d status %d p->status %d", mode, status, p->status);
+
+ if (status == NO_ERROR) {
+ status = p->status;
+ if (status == NO_ERROR) {
+ mMeasurementMode = mode;
+ }
+ }
+ return status;
+}
+
+status_t Visualizer::getIntMeasurements(uint32_t type, uint32_t number, int32_t *measurements) {
+ if (mMeasurementMode == MEASUREMENT_MODE_NONE) {
+ ALOGE("Cannot retrieve int measurements, no measurement mode set");
+ return INVALID_OPERATION;
+ }
+ if (!(mMeasurementMode & type)) {
+ // measurement type has not been set on this Visualizer
+ ALOGE("Cannot retrieve int measurements, requested measurement mode 0x%x not set(0x%x)",
+ type, mMeasurementMode);
+ return INVALID_OPERATION;
+ }
+ // only peak+RMS measurement supported
+ if ((type != MEASUREMENT_MODE_PEAK_RMS)
+ // for peak+RMS measurement, the results are 2 int32_t values
+ || (number != 2)) {
+ ALOGE("Cannot retrieve int measurements, MEASUREMENT_MODE_PEAK_RMS returns 2 ints, not %d",
+ number);
+ return BAD_VALUE;
+ }
+
+ status_t status = NO_ERROR;
+ if (mEnabled) {
+ uint32_t replySize = number * sizeof(int32_t);
+ status = command(VISUALIZER_CMD_MEASURE,
+ sizeof(uint32_t) /*cmdSize*/,
+ &type /*cmdData*/,
+ &replySize, measurements);
+ ALOGV("getMeasurements() command returned %d", status);
+ if ((status == NO_ERROR) && (replySize == 0)) {
+ status = NOT_ENOUGH_DATA;
+ }
+ } else {
+ ALOGV("getMeasurements() disabled");
+ return INVALID_OPERATION;
+ }
+ return status;
+}
+
status_t Visualizer::getWaveForm(uint8_t *waveform)
{
if (waveform == NULL) {
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 963b04f..0f6d897 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -756,6 +756,9 @@ void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
case MEDIA_TIMED_TEXT:
ALOGV("Received timed text message");
break;
+ case MEDIA_SUBTITLE_DATA:
+ ALOGV("Received subtitle data message");
+ break;
default:
ALOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2);
break;
@@ -773,17 +776,20 @@ void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
}
}
-/*static*/ sp<IMemory> MediaPlayer::decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat)
+/*static*/ status_t MediaPlayer::decode(const char* url, uint32_t *pSampleRate,
+ int* pNumChannels, audio_format_t* pFormat,
+ const sp<IMemoryHeap>& heap, size_t *pSize)
{
ALOGV("decode(%s)", url);
- sp<IMemory> p;
+ status_t status;
const sp<IMediaPlayerService>& service = getMediaPlayerService();
if (service != 0) {
- p = service->decode(url, pSampleRate, pNumChannels, pFormat);
+ status = service->decode(url, pSampleRate, pNumChannels, pFormat, heap, pSize);
} else {
ALOGE("Unable to locate media service");
+ status = DEAD_OBJECT;
}
- return p;
+ return status;
}
@@ -793,17 +799,22 @@ void MediaPlayer::died()
notify(MEDIA_ERROR, MEDIA_ERROR_SERVER_DIED, 0);
}
-/*static*/ sp<IMemory> MediaPlayer::decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat)
+/*static*/ status_t MediaPlayer::decode(int fd, int64_t offset, int64_t length,
+ uint32_t *pSampleRate, int* pNumChannels,
+ audio_format_t* pFormat,
+ const sp<IMemoryHeap>& heap, size_t *pSize)
{
ALOGV("decode(%d, %lld, %lld)", fd, offset, length);
- sp<IMemory> p;
+ status_t status;
const sp<IMediaPlayerService>& service = getMediaPlayerService();
if (service != 0) {
- p = service->decode(fd, offset, length, pSampleRate, pNumChannels, pFormat);
+ status = service->decode(fd, offset, length, pSampleRate,
+ pNumChannels, pFormat, heap, pSize);
} else {
ALOGE("Unable to locate media service");
+ status = DEAD_OBJECT;
}
- return p;
+ return status;
}
@@ -811,6 +822,13 @@ status_t MediaPlayer::setNextMediaPlayer(const sp<MediaPlayer>& next) {
if (mPlayer == NULL) {
return NO_INIT;
}
+
+ if (next != NULL && !(next->mCurrentState &
+ (MEDIA_PLAYER_PREPARED | MEDIA_PLAYER_PAUSED | MEDIA_PLAYER_PLAYBACK_COMPLETE))) {
+ ALOGE("next player is not prepared");
+ return INVALID_OPERATION;
+ }
+
return mPlayer->setNextPlayer(next == NULL ? NULL : next->mPlayer);
}
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index d87bc7f..8f21632 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -34,6 +34,7 @@ LOCAL_SHARED_LIBRARIES := \
libsonivox \
libstagefright \
libstagefright_foundation \
+ libstagefright_httplive \
libstagefright_omx \
libstagefright_wfd \
libutils \
diff --git a/media/libmediaplayerservice/Crypto.cpp b/media/libmediaplayerservice/Crypto.cpp
index ae4d845..62593b2 100644
--- a/media/libmediaplayerservice/Crypto.cpp
+++ b/media/libmediaplayerservice/Crypto.cpp
@@ -134,7 +134,6 @@ void Crypto::findFactoryForScheme(const uint8_t uuid[16]) {
return;
}
- ALOGE("Failed to find crypto plugin");
mInitCheck = ERROR_UNSUPPORTED;
}
@@ -151,6 +150,7 @@ bool Crypto::loadLibraryForScheme(const String8 &path, const uint8_t uuid[16]) {
if (!mLibrary.get()) {
mLibrary = new SharedLibrary(path);
if (!*mLibrary) {
+ ALOGE("loadLibraryForScheme failed:%s", mLibrary->lastError());
return false;
}
@@ -165,6 +165,7 @@ bool Crypto::loadLibraryForScheme(const String8 &path, const uint8_t uuid[16]) {
if (createCryptoFactory == NULL ||
(mFactory = createCryptoFactory()) == NULL ||
!mFactory->isCryptoSchemeSupported(uuid)) {
+ ALOGE("createCryptoFactory failed:%s", mLibrary->lastError());
closeFactory();
return false;
}
diff --git a/media/libmediaplayerservice/Drm.cpp b/media/libmediaplayerservice/Drm.cpp
index f00f488..eebcb79 100644
--- a/media/libmediaplayerservice/Drm.cpp
+++ b/media/libmediaplayerservice/Drm.cpp
@@ -211,15 +211,22 @@ bool Drm::loadLibraryForScheme(const String8 &path, const uint8_t uuid[16]) {
return true;
}
-bool Drm::isCryptoSchemeSupported(const uint8_t uuid[16]) {
+bool Drm::isCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) {
+
Mutex::Autolock autoLock(mLock);
- if (mFactory && mFactory->isCryptoSchemeSupported(uuid)) {
- return true;
+ if (!mFactory || !mFactory->isCryptoSchemeSupported(uuid)) {
+ findFactoryForScheme(uuid);
+ if (mInitCheck != OK) {
+ return false;
+ }
+ }
+
+ if (mimeType != "") {
+ return mFactory->isContentTypeSupported(mimeType);
}
- findFactoryForScheme(uuid);
- return (mInitCheck == OK);
+ return true;
}
status_t Drm::createPlugin(const uint8_t uuid[16]) {
diff --git a/media/libmediaplayerservice/Drm.h b/media/libmediaplayerservice/Drm.h
index 3f460f1..119fd50 100644
--- a/media/libmediaplayerservice/Drm.h
+++ b/media/libmediaplayerservice/Drm.h
@@ -37,7 +37,7 @@ struct Drm : public BnDrm,
virtual status_t initCheck() const;
- virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]);
+ virtual bool isCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType);
virtual status_t createPlugin(const uint8_t uuid[16]);
diff --git a/media/libmediaplayerservice/HDCP.cpp b/media/libmediaplayerservice/HDCP.cpp
index 469a02e..c2ac1a3 100644
--- a/media/libmediaplayerservice/HDCP.cpp
+++ b/media/libmediaplayerservice/HDCP.cpp
@@ -100,6 +100,20 @@ status_t HDCP::shutdownAsync() {
return mHDCPModule->shutdownAsync();
}
+uint32_t HDCP::getCaps() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mHDCPModule == NULL) {
+ return NO_INIT;
+ }
+
+ // TO-DO:
+ // Only support HDCP_CAPS_ENCRYPT (byte-array to byte-array) for now.
+ // use mHDCPModule->getCaps() when the HDCP libraries get updated.
+ //return mHDCPModule->getCaps();
+ return HDCPModule::HDCP_CAPS_ENCRYPT;
+}
+
status_t HDCP::encrypt(
const void *inData, size_t size, uint32_t streamCTR,
uint64_t *outInputCTR, void *outData) {
@@ -116,6 +130,24 @@ status_t HDCP::encrypt(
return mHDCPModule->encrypt(inData, size, streamCTR, outInputCTR, outData);
}
+status_t HDCP::encryptNative(
+ const sp<GraphicBuffer> &graphicBuffer,
+ size_t offset, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData) {
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(mIsEncryptionModule);
+
+ if (mHDCPModule == NULL) {
+ *outInputCTR = 0;
+
+ return NO_INIT;
+ }
+
+ return mHDCPModule->encryptNative(graphicBuffer->handle,
+ offset, size, streamCTR, outInputCTR, outData);
+}
+
status_t HDCP::decrypt(
const void *inData, size_t size,
uint32_t streamCTR, uint64_t outInputCTR, void *outData) {
diff --git a/media/libmediaplayerservice/HDCP.h b/media/libmediaplayerservice/HDCP.h
index 42e6467..26ddc86 100644
--- a/media/libmediaplayerservice/HDCP.h
+++ b/media/libmediaplayerservice/HDCP.h
@@ -30,11 +30,17 @@ struct HDCP : public BnHDCP {
virtual status_t setObserver(const sp<IHDCPObserver> &observer);
virtual status_t initAsync(const char *host, unsigned port);
virtual status_t shutdownAsync();
+ virtual uint32_t getCaps();
virtual status_t encrypt(
const void *inData, size_t size, uint32_t streamCTR,
uint64_t *outInputCTR, void *outData);
+ virtual status_t encryptNative(
+ const sp<GraphicBuffer> &graphicBuffer,
+ size_t offset, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData);
+
virtual status_t decrypt(
const void *inData, size_t size,
uint32_t streamCTR, uint64_t outInputCTR, void *outData);
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 57ec7ea..9553458 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -53,6 +53,8 @@
#include <media/AudioTrack.h>
#include <media/MemoryLeakTrackUtil.h>
#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/AudioPlayer.h>
+#include <media/stagefright/foundation/ADebug.h>
#include <system/audio.h>
@@ -317,8 +319,8 @@ status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& ar
result.append(" AudioCache\n");
if (mHeap != 0) {
- snprintf(buffer, 255, " heap base(%p), size(%d), flags(%d), device(%s)\n",
- mHeap->getBase(), mHeap->getSize(), mHeap->getFlags(), mHeap->getDevice());
+ snprintf(buffer, 255, " heap base(%p), size(%d), flags(%d)\n",
+ mHeap->getBase(), mHeap->getSize(), mHeap->getFlags());
result.append(buffer);
}
snprintf(buffer, 255, " msec per frame(%f), channel count(%d), format(%d), frame count(%zd)\n",
@@ -742,7 +744,7 @@ status_t MediaPlayerService::Client::setVideoSurfaceTexture(
sp<ANativeWindow> anw;
if (bufferProducer != NULL) {
- anw = new Surface(bufferProducer);
+ anw = new Surface(bufferProducer, true /* controlledByApp */);
status_t err = native_window_api_connect(anw.get(),
NATIVE_WINDOW_API_MEDIA);
@@ -1174,13 +1176,13 @@ int Antagonizer::callbackThread(void* user)
}
#endif
-static size_t kDefaultHeapSize = 1024 * 1024; // 1MB
-
-sp<IMemory> MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat)
+status_t MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, int* pNumChannels,
+ audio_format_t* pFormat,
+ const sp<IMemoryHeap>& heap, size_t *pSize)
{
ALOGV("decode(%s)", url);
- sp<MemoryBase> mem;
sp<MediaPlayerBase> player;
+ status_t status = BAD_VALUE;
// Protect our precious, precious DRMd ringtones by only allowing
// decoding of http, but not filesystem paths or content Uris.
@@ -1188,7 +1190,7 @@ sp<IMemory> MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, i
// filedescriptor for them and use that.
if (url != NULL && strncmp(url, "http://", 7) != 0) {
ALOGD("Can't decode %s by path, use filedescriptor instead", url);
- return mem;
+ return BAD_VALUE;
}
player_type playerType =
@@ -1196,7 +1198,7 @@ sp<IMemory> MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, i
ALOGV("player type = %d", playerType);
// create the right type of player
- sp<AudioCache> cache = new AudioCache(url);
+ sp<AudioCache> cache = new AudioCache(heap);
player = MediaPlayerFactory::createPlayer(playerType, cache.get(), cache->notify);
if (player == NULL) goto Exit;
if (player->hardwareOutput()) goto Exit;
@@ -1222,22 +1224,27 @@ sp<IMemory> MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, i
goto Exit;
}
- mem = new MemoryBase(cache->getHeap(), 0, cache->size());
+ *pSize = cache->size();
*pSampleRate = cache->sampleRate();
*pNumChannels = cache->channelCount();
*pFormat = cache->format();
- ALOGV("return memory @ %p, sampleRate=%u, channelCount = %d, format = %d", mem->pointer(), *pSampleRate, *pNumChannels, *pFormat);
+ ALOGV("return size %d sampleRate=%u, channelCount = %d, format = %d",
+ *pSize, *pSampleRate, *pNumChannels, *pFormat);
+ status = NO_ERROR;
Exit:
if (player != 0) player->reset();
- return mem;
+ return status;
}
-sp<IMemory> MediaPlayerService::decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat)
+status_t MediaPlayerService::decode(int fd, int64_t offset, int64_t length,
+ uint32_t *pSampleRate, int* pNumChannels,
+ audio_format_t* pFormat,
+ const sp<IMemoryHeap>& heap, size_t *pSize)
{
ALOGV("decode(%d, %lld, %lld)", fd, offset, length);
- sp<MemoryBase> mem;
sp<MediaPlayerBase> player;
+ status_t status = BAD_VALUE;
player_type playerType = MediaPlayerFactory::getPlayerType(NULL /* client */,
fd,
@@ -1246,7 +1253,7 @@ sp<IMemory> MediaPlayerService::decode(int fd, int64_t offset, int64_t length, u
ALOGV("player type = %d", playerType);
// create the right type of player
- sp<AudioCache> cache = new AudioCache("decode_fd");
+ sp<AudioCache> cache = new AudioCache(heap);
player = MediaPlayerFactory::createPlayer(playerType, cache.get(), cache->notify);
if (player == NULL) goto Exit;
if (player->hardwareOutput()) goto Exit;
@@ -1272,16 +1279,18 @@ sp<IMemory> MediaPlayerService::decode(int fd, int64_t offset, int64_t length, u
goto Exit;
}
- mem = new MemoryBase(cache->getHeap(), 0, cache->size());
+ *pSize = cache->size();
*pSampleRate = cache->sampleRate();
*pNumChannels = cache->channelCount();
*pFormat = cache->format();
- ALOGV("return memory @ %p, sampleRate=%u, channelCount = %d, format = %d", mem->pointer(), *pSampleRate, *pNumChannels, *pFormat);
+ ALOGV("return size %d, sampleRate=%u, channelCount = %d, format = %d",
+ *pSize, *pSampleRate, *pNumChannels, *pFormat);
+ status = NO_ERROR;
Exit:
if (player != 0) player->reset();
::close(fd);
- return mem;
+ return status;
}
@@ -1295,8 +1304,6 @@ MediaPlayerService::AudioOutput::AudioOutput(int sessionId)
mSessionId(sessionId),
mFlags(AUDIO_OUTPUT_FLAG_NONE) {
ALOGV("AudioOutput(%d)", sessionId);
- mTrack = 0;
- mRecycledTrack = 0;
mStreamType = AUDIO_STREAM_MUSIC;
mLeftVolume = 1.0;
mRightVolume = 1.0;
@@ -1311,7 +1318,6 @@ MediaPlayerService::AudioOutput::AudioOutput(int sessionId)
MediaPlayerService::AudioOutput::~AudioOutput()
{
close();
- delete mRecycledTrack;
delete mCallbackData;
}
@@ -1384,11 +1390,51 @@ status_t MediaPlayerService::AudioOutput::getFramesWritten(uint32_t *frameswritt
return OK;
}
+status_t MediaPlayerService::AudioOutput::setParameters(const String8& keyValuePairs)
+{
+ if (mTrack == 0) return NO_INIT;
+ return mTrack->setParameters(keyValuePairs);
+}
+
+String8 MediaPlayerService::AudioOutput::getParameters(const String8& keys)
+{
+ if (mTrack == 0) return String8::empty();
+ return mTrack->getParameters(keys);
+}
+
+void MediaPlayerService::AudioOutput::deleteRecycledTrack()
+{
+ ALOGV("deleteRecycledTrack");
+
+ if (mRecycledTrack != 0) {
+
+ if (mCallbackData != NULL) {
+ mCallbackData->setOutput(NULL);
+ mCallbackData->endTrackSwitch();
+ }
+
+ if ((mRecycledTrack->getFlags() & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) == 0) {
+ mRecycledTrack->flush();
+ }
+ // An offloaded track isn't flushed because the STREAM_END is reported
+ // slightly prematurely to allow time for the gapless track switch
+ // but this means that if we decide not to recycle the track there
+ // could be a small amount of residual data still playing. We leave
+ // AudioFlinger to drain the track.
+
+ mRecycledTrack.clear();
+ delete mCallbackData;
+ mCallbackData = NULL;
+ close();
+ }
+}
+
status_t MediaPlayerService::AudioOutput::open(
uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask,
audio_format_t format, int bufferCount,
AudioCallback cb, void *cookie,
- audio_output_flags_t flags)
+ audio_output_flags_t flags,
+ const audio_offload_info_t *offloadInfo)
{
mCallback = cb;
mCallbackCookie = cookie;
@@ -1399,20 +1445,34 @@ status_t MediaPlayerService::AudioOutput::open(
bufferCount = mMinBufferCount;
}
- ALOGV("open(%u, %d, 0x%x, %d, %d, %d)", sampleRate, channelCount, channelMask,
- format, bufferCount, mSessionId);
+ ALOGV("open(%u, %d, 0x%x, 0x%x, %d, %d 0x%x)", sampleRate, channelCount, channelMask,
+ format, bufferCount, mSessionId, flags);
uint32_t afSampleRate;
size_t afFrameCount;
uint32_t frameCount;
- if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) {
- return NO_INIT;
- }
- if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) {
- return NO_INIT;
+ // offloading is only supported in callback mode for now.
+ // offloadInfo must be present if offload flag is set
+ if (((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0) &&
+ ((cb == NULL) || (offloadInfo == NULL))) {
+ return BAD_VALUE;
}
- frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate;
+ if ((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0) {
+ frameCount = 0; // AudioTrack will get frame count from AudioFlinger
+ } else {
+ uint32_t afSampleRate;
+ size_t afFrameCount;
+
+ if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) {
+ return NO_INIT;
+ }
+ if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) {
+ return NO_INIT;
+ }
+
+ frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate;
+ }
if (channelMask == CHANNEL_MASK_USE_CHANNEL_ORDER) {
channelMask = audio_channel_out_mask_from_count(channelCount);
@@ -1422,90 +1482,127 @@ status_t MediaPlayerService::AudioOutput::open(
}
}
- AudioTrack *t;
- CallbackData *newcbd = NULL;
- if (mCallback != NULL) {
- newcbd = new CallbackData(this);
- t = new AudioTrack(
- mStreamType,
- sampleRate,
- format,
- channelMask,
- frameCount,
- flags,
- CallbackWrapper,
- newcbd,
- 0, // notification frames
- mSessionId);
- } else {
- t = new AudioTrack(
- mStreamType,
- sampleRate,
- format,
- channelMask,
- frameCount,
- flags,
- NULL,
- NULL,
- 0,
- mSessionId);
- }
-
- if ((t == 0) || (t->initCheck() != NO_ERROR)) {
- ALOGE("Unable to create audio track");
- delete t;
- delete newcbd;
- return NO_INIT;
- }
+ // Check whether we can recycle the track
+ bool reuse = false;
+ bool bothOffloaded = false;
+ if (mRecycledTrack != 0) {
+ // check whether we are switching between two offloaded tracks
+ bothOffloaded = (flags & mRecycledTrack->getFlags()
+ & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0;
- if (mRecycledTrack) {
// check if the existing track can be reused as-is, or if a new track needs to be created.
+ reuse = true;
- bool reuse = true;
if ((mCallbackData == NULL && mCallback != NULL) ||
(mCallbackData != NULL && mCallback == NULL)) {
// recycled track uses callbacks but the caller wants to use writes, or vice versa
ALOGV("can't chain callback and write");
reuse = false;
} else if ((mRecycledTrack->getSampleRate() != sampleRate) ||
- (mRecycledTrack->channelCount() != channelCount) ||
- (mRecycledTrack->frameCount() != t->frameCount())) {
- ALOGV("samplerate, channelcount or framecount differ: %d/%d Hz, %d/%d ch, %d/%d frames",
+ (mRecycledTrack->channelCount() != (uint32_t)channelCount) ) {
+ ALOGV("samplerate, channelcount differ: %u/%u Hz, %u/%d ch",
mRecycledTrack->getSampleRate(), sampleRate,
- mRecycledTrack->channelCount(), channelCount,
- mRecycledTrack->frameCount(), t->frameCount());
+ mRecycledTrack->channelCount(), channelCount);
reuse = false;
} else if (flags != mFlags) {
ALOGV("output flags differ %08x/%08x", flags, mFlags);
reuse = false;
+ } else if (mRecycledTrack->format() != format) {
+ reuse = false;
}
+ } else {
+ ALOGV("no track available to recycle");
+ }
+
+ ALOGV_IF(bothOffloaded, "both tracks offloaded");
+
+ // If we can't recycle and both tracks are offloaded
+ // we must close the previous output before opening a new one
+ if (bothOffloaded && !reuse) {
+ ALOGV("both offloaded and not recycling");
+ deleteRecycledTrack();
+ }
+
+ sp<AudioTrack> t;
+ CallbackData *newcbd = NULL;
+
+ // We don't attempt to create a new track if we are recycling an
+ // offloaded track. But, if we are recycling a non-offloaded or we
+ // are switching where one is offloaded and one isn't then we create
+ // the new track in advance so that we can read additional stream info
+
+ if (!(reuse && bothOffloaded)) {
+ ALOGV("creating new AudioTrack");
+
+ if (mCallback != NULL) {
+ newcbd = new CallbackData(this);
+ t = new AudioTrack(
+ mStreamType,
+ sampleRate,
+ format,
+ channelMask,
+ frameCount,
+ flags,
+ CallbackWrapper,
+ newcbd,
+ 0, // notification frames
+ mSessionId,
+ AudioTrack::TRANSFER_CALLBACK,
+ offloadInfo);
+ } else {
+ t = new AudioTrack(
+ mStreamType,
+ sampleRate,
+ format,
+ channelMask,
+ frameCount,
+ flags,
+ NULL,
+ NULL,
+ 0,
+ mSessionId);
+ }
+
+ if ((t == 0) || (t->initCheck() != NO_ERROR)) {
+ ALOGE("Unable to create audio track");
+ delete newcbd;
+ return NO_INIT;
+ }
+ }
+
+ if (reuse) {
+ CHECK(mRecycledTrack != NULL);
+
+ if (!bothOffloaded) {
+ if (mRecycledTrack->frameCount() != t->frameCount()) {
+ ALOGV("framecount differs: %u/%u frames",
+ mRecycledTrack->frameCount(), t->frameCount());
+ reuse = false;
+ }
+ }
+
if (reuse) {
- ALOGV("chaining to next output");
+ ALOGV("chaining to next output and recycling track");
close();
mTrack = mRecycledTrack;
- mRecycledTrack = NULL;
+ mRecycledTrack.clear();
if (mCallbackData != NULL) {
mCallbackData->setOutput(this);
}
- delete t;
delete newcbd;
return OK;
}
+ }
- // if we're not going to reuse the track, unblock and flush it
- if (mCallbackData != NULL) {
- mCallbackData->setOutput(NULL);
- mCallbackData->endTrackSwitch();
- }
- mRecycledTrack->flush();
- delete mRecycledTrack;
- mRecycledTrack = NULL;
- delete mCallbackData;
- mCallbackData = NULL;
- close();
+ // we're not going to reuse the track, unblock and flush it
+ // this was done earlier if both tracks are offloaded
+ if (!bothOffloaded) {
+ deleteRecycledTrack();
}
+ CHECK((t != NULL) && ((mCallback == NULL) || (newcbd != NULL)));
+
mCallbackData = newcbd;
ALOGV("setVolume");
t->setVolume(mLeftVolume, mRightVolume);
@@ -1519,25 +1616,30 @@ status_t MediaPlayerService::AudioOutput::open(
}
mTrack = t;
- status_t res = t->setSampleRate(mPlaybackRatePermille * mSampleRateHz / 1000);
- if (res != NO_ERROR) {
- return res;
+ status_t res = NO_ERROR;
+ if ((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) == 0) {
+ res = t->setSampleRate(mPlaybackRatePermille * mSampleRateHz / 1000);
+ if (res == NO_ERROR) {
+ t->setAuxEffectSendLevel(mSendLevel);
+ res = t->attachAuxEffect(mAuxEffectId);
+ }
}
- t->setAuxEffectSendLevel(mSendLevel);
- return t->attachAuxEffect(mAuxEffectId);;
+ ALOGV("open() DONE status %d", res);
+ return res;
}
-void MediaPlayerService::AudioOutput::start()
+status_t MediaPlayerService::AudioOutput::start()
{
ALOGV("start");
if (mCallbackData != NULL) {
mCallbackData->endTrackSwitch();
}
- if (mTrack) {
+ if (mTrack != 0) {
mTrack->setVolume(mLeftVolume, mRightVolume);
mTrack->setAuxEffectSendLevel(mSendLevel);
- mTrack->start();
+ return mTrack->start();
}
+ return NO_INIT;
}
void MediaPlayerService::AudioOutput::setNextOutput(const sp<AudioOutput>& nextOutput) {
@@ -1555,7 +1657,7 @@ void MediaPlayerService::AudioOutput::switchToNextOutput() {
mNextOutput->mCallbackData = mCallbackData;
mCallbackData = NULL;
mNextOutput->mRecycledTrack = mTrack;
- mTrack = NULL;
+ mTrack.clear();
mNextOutput->mSampleRateHz = mSampleRateHz;
mNextOutput->mMsecsPerFrame = mMsecsPerFrame;
mNextOutput->mBytesWritten = mBytesWritten;
@@ -1568,7 +1670,7 @@ ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size)
LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback.");
//ALOGV("write(%p, %u)", buffer, size);
- if (mTrack) {
+ if (mTrack != 0) {
ssize_t ret = mTrack->write(buffer, size);
mBytesWritten += ret;
return ret;
@@ -1579,26 +1681,25 @@ ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size)
void MediaPlayerService::AudioOutput::stop()
{
ALOGV("stop");
- if (mTrack) mTrack->stop();
+ if (mTrack != 0) mTrack->stop();
}
void MediaPlayerService::AudioOutput::flush()
{
ALOGV("flush");
- if (mTrack) mTrack->flush();
+ if (mTrack != 0) mTrack->flush();
}
void MediaPlayerService::AudioOutput::pause()
{
ALOGV("pause");
- if (mTrack) mTrack->pause();
+ if (mTrack != 0) mTrack->pause();
}
void MediaPlayerService::AudioOutput::close()
{
ALOGV("close");
- delete mTrack;
- mTrack = 0;
+ mTrack.clear();
}
void MediaPlayerService::AudioOutput::setVolume(float left, float right)
@@ -1606,7 +1707,7 @@ void MediaPlayerService::AudioOutput::setVolume(float left, float right)
ALOGV("setVolume(%f, %f)", left, right);
mLeftVolume = left;
mRightVolume = right;
- if (mTrack) {
+ if (mTrack != 0) {
mTrack->setVolume(left, right);
}
}
@@ -1615,7 +1716,7 @@ status_t MediaPlayerService::AudioOutput::setPlaybackRatePermille(int32_t ratePe
{
ALOGV("setPlaybackRatePermille(%d)", ratePermille);
status_t res = NO_ERROR;
- if (mTrack) {
+ if (mTrack != 0) {
res = mTrack->setSampleRate(ratePermille * mSampleRateHz / 1000);
} else {
res = NO_INIT;
@@ -1631,7 +1732,7 @@ status_t MediaPlayerService::AudioOutput::setAuxEffectSendLevel(float level)
{
ALOGV("setAuxEffectSendLevel(%f)", level);
mSendLevel = level;
- if (mTrack) {
+ if (mTrack != 0) {
return mTrack->setAuxEffectSendLevel(level);
}
return NO_ERROR;
@@ -1641,7 +1742,7 @@ status_t MediaPlayerService::AudioOutput::attachAuxEffect(int effectId)
{
ALOGV("attachAuxEffect(%d)", effectId);
mAuxEffectId = effectId;
- if (mTrack) {
+ if (mTrack != 0) {
return mTrack->attachAuxEffect(effectId);
}
return NO_ERROR;
@@ -1651,10 +1752,6 @@ status_t MediaPlayerService::AudioOutput::attachAuxEffect(int effectId)
void MediaPlayerService::AudioOutput::CallbackWrapper(
int event, void *cookie, void *info) {
//ALOGV("callbackwrapper");
- if (event != AudioTrack::EVENT_MORE_DATA) {
- return;
- }
-
CallbackData *data = (CallbackData*)cookie;
data->lock();
AudioOutput *me = data->getOutput();
@@ -1663,22 +1760,46 @@ void MediaPlayerService::AudioOutput::CallbackWrapper(
// no output set, likely because the track was scheduled to be reused
// by another player, but the format turned out to be incompatible.
data->unlock();
- buffer->size = 0;
+ if (buffer != NULL) {
+ buffer->size = 0;
+ }
return;
}
- size_t actualSize = (*me->mCallback)(
- me, buffer->raw, buffer->size, me->mCallbackCookie);
+ switch(event) {
+ case AudioTrack::EVENT_MORE_DATA: {
+ size_t actualSize = (*me->mCallback)(
+ me, buffer->raw, buffer->size, me->mCallbackCookie,
+ CB_EVENT_FILL_BUFFER);
+
+ if (actualSize == 0 && buffer->size > 0 && me->mNextOutput == NULL) {
+ // We've reached EOS but the audio track is not stopped yet,
+ // keep playing silence.
+
+ memset(buffer->raw, 0, buffer->size);
+ actualSize = buffer->size;
+ }
+
+ buffer->size = actualSize;
+ } break;
- if (actualSize == 0 && buffer->size > 0 && me->mNextOutput == NULL) {
- // We've reached EOS but the audio track is not stopped yet,
- // keep playing silence.
- memset(buffer->raw, 0, buffer->size);
- actualSize = buffer->size;
+ case AudioTrack::EVENT_STREAM_END:
+ ALOGV("callbackwrapper: deliver EVENT_STREAM_END");
+ (*me->mCallback)(me, NULL /* buffer */, 0 /* size */,
+ me->mCallbackCookie, CB_EVENT_STREAM_END);
+ break;
+
+ case AudioTrack::EVENT_NEW_IAUDIOTRACK :
+ ALOGV("callbackwrapper: deliver EVENT_TEAR_DOWN");
+ (*me->mCallback)(me, NULL /* buffer */, 0 /* size */,
+ me->mCallbackCookie, CB_EVENT_TEAR_DOWN);
+ break;
+
+ default:
+ ALOGE("received unknown event type: %d inside CallbackWrapper !", event);
}
- buffer->size = actualSize;
data->unlock();
}
@@ -1689,12 +1810,10 @@ int MediaPlayerService::AudioOutput::getSessionId() const
#undef LOG_TAG
#define LOG_TAG "AudioCache"
-MediaPlayerService::AudioCache::AudioCache(const char* name) :
- mChannelCount(0), mFrameCount(1024), mSampleRate(0), mSize(0),
- mError(NO_ERROR), mCommandComplete(false)
+MediaPlayerService::AudioCache::AudioCache(const sp<IMemoryHeap>& heap) :
+ mHeap(heap), mChannelCount(0), mFrameCount(1024), mSampleRate(0), mSize(0),
+ mError(NO_ERROR), mCommandComplete(false)
{
- // create ashmem heap
- mHeap = new MemoryHeapBase(kDefaultHeapSize, 0, name);
}
uint32_t MediaPlayerService::AudioCache::latency () const
@@ -1774,7 +1893,8 @@ bool CallbackThread::threadLoop() {
}
size_t actualSize =
- (*mCallback)(sink.get(), mBuffer, mBufferSize, mCookie);
+ (*mCallback)(sink.get(), mBuffer, mBufferSize, mCookie,
+ MediaPlayerBase::AudioSink::CB_EVENT_FILL_BUFFER);
if (actualSize > 0) {
sink->write(mBuffer, actualSize);
@@ -1788,7 +1908,8 @@ bool CallbackThread::threadLoop() {
status_t MediaPlayerService::AudioCache::open(
uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask,
audio_format_t format, int bufferCount,
- AudioCallback cb, void *cookie, audio_output_flags_t flags)
+ AudioCallback cb, void *cookie, audio_output_flags_t flags,
+ const audio_offload_info_t *offloadInfo)
{
ALOGV("open(%u, %d, 0x%x, %d, %d)", sampleRate, channelCount, channelMask, format, bufferCount);
if (mHeap->getHeapID() < 0) {
@@ -1806,10 +1927,11 @@ status_t MediaPlayerService::AudioCache::open(
return NO_ERROR;
}
-void MediaPlayerService::AudioCache::start() {
+status_t MediaPlayerService::AudioCache::start() {
if (mCallbackThread != NULL) {
mCallbackThread->run("AudioCache callback");
}
+ return NO_ERROR;
}
void MediaPlayerService::AudioCache::stop() {
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index b33805d..21f4117 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -20,15 +20,12 @@
#include <arpa/inet.h>
-#include <utils/Log.h>
#include <utils/threads.h>
-#include <utils/List.h>
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
#include <utils/String8.h>
#include <utils/Vector.h>
-#include <media/IMediaPlayerService.h>
#include <media/MediaPlayerInterface.h>
#include <media/Metadata.h>
#include <media/stagefright/foundation/ABase.h>
@@ -78,7 +75,7 @@ class MediaPlayerService : public BnMediaPlayerService
AudioOutput(int sessionId);
virtual ~AudioOutput();
- virtual bool ready() const { return mTrack != NULL; }
+ virtual bool ready() const { return mTrack != 0; }
virtual bool realtime() const { return true; }
virtual ssize_t bufferSize() const;
virtual ssize_t frameCount() const;
@@ -94,9 +91,10 @@ class MediaPlayerService : public BnMediaPlayerService
uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask,
audio_format_t format, int bufferCount,
AudioCallback cb, void *cookie,
- audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE);
+ audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE,
+ const audio_offload_info_t *offloadInfo = NULL);
- virtual void start();
+ virtual status_t start();
virtual ssize_t write(const void* buffer, size_t size);
virtual void stop();
virtual void flush();
@@ -114,14 +112,17 @@ class MediaPlayerService : public BnMediaPlayerService
void setNextOutput(const sp<AudioOutput>& nextOutput);
void switchToNextOutput();
virtual bool needsTrailingPadding() { return mNextOutput == NULL; }
+ virtual status_t setParameters(const String8& keyValuePairs);
+ virtual String8 getParameters(const String8& keys);
private:
static void setMinBufferCount();
static void CallbackWrapper(
int event, void *me, void *info);
+ void deleteRecycledTrack();
- AudioTrack* mTrack;
- AudioTrack* mRecycledTrack;
+ sp<AudioTrack> mTrack;
+ sp<AudioTrack> mRecycledTrack;
sp<AudioOutput> mNextOutput;
AudioCallback mCallback;
void * mCallbackCookie;
@@ -176,7 +177,7 @@ class MediaPlayerService : public BnMediaPlayerService
class AudioCache : public MediaPlayerBase::AudioSink
{
public:
- AudioCache(const char* name);
+ AudioCache(const sp<IMemoryHeap>& heap);
virtual ~AudioCache() {}
virtual bool ready() const { return (mChannelCount > 0) && (mHeap->getHeapID() > 0); }
@@ -195,9 +196,10 @@ class MediaPlayerService : public BnMediaPlayerService
uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask,
audio_format_t format, int bufferCount = 1,
AudioCallback cb = NULL, void *cookie = NULL,
- audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE);
+ audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE,
+ const audio_offload_info_t *offloadInfo = NULL);
- virtual void start();
+ virtual status_t start();
virtual ssize_t write(const void* buffer, size_t size);
virtual void stop();
virtual void flush() {}
@@ -222,7 +224,7 @@ class MediaPlayerService : public BnMediaPlayerService
Mutex mLock;
Condition mSignal;
- sp<MemoryHeapBase> mHeap;
+ sp<IMemoryHeap> mHeap;
float mMsecsPerFrame;
uint16_t mChannelCount;
audio_format_t mFormat;
@@ -245,8 +247,13 @@ public:
virtual sp<IMediaPlayer> create(const sp<IMediaPlayerClient>& client, int audioSessionId);
- virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
- virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
+ virtual status_t decode(const char* url, uint32_t *pSampleRate, int* pNumChannels,
+ audio_format_t* pFormat,
+ const sp<IMemoryHeap>& heap, size_t *pSize);
+ virtual status_t decode(int fd, int64_t offset, int64_t length,
+ uint32_t *pSampleRate, int* pNumChannels,
+ audio_format_t* pFormat,
+ const sp<IMemoryHeap>& heap, size_t *pSize);
virtual sp<IOMX> getOMX();
virtual sp<ICrypto> makeCrypto();
virtual sp<IDrm> makeDrm();
diff --git a/media/libmediaplayerservice/MidiFile.cpp b/media/libmediaplayerservice/MidiFile.cpp
index 8db5b9b..0a6aa90 100644
--- a/media/libmediaplayerservice/MidiFile.cpp
+++ b/media/libmediaplayerservice/MidiFile.cpp
@@ -220,6 +220,9 @@ status_t MidiFile::start()
}
mRender = true;
+ if (mState == EAS_STATE_PLAY) {
+ sendEvent(MEDIA_STARTED);
+ }
// wake up render thread
ALOGV(" wakeup render thread");
@@ -242,6 +245,7 @@ status_t MidiFile::stop()
}
}
mPaused = false;
+ sendEvent(MEDIA_STOPPED);
return NO_ERROR;
}
@@ -279,6 +283,7 @@ status_t MidiFile::pause()
return ERROR_EAS_FAILURE;
}
mPaused = true;
+ sendEvent(MEDIA_PAUSED);
return NO_ERROR;
}
@@ -382,6 +387,7 @@ status_t MidiFile::reset()
status_t MidiFile::reset_nosync()
{
ALOGV("MidiFile::reset_nosync");
+ sendEvent(MEDIA_STOPPED);
// close file
if (mEasHandle) {
EAS_CloseFile(mEasData, mEasHandle);
@@ -422,7 +428,7 @@ status_t MidiFile::setLooping(int loop)
status_t MidiFile::createOutputTrack() {
if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels,
- CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) {
+ CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2 /*bufferCount*/) != NO_ERROR) {
ALOGE("mAudioSink open failed");
return ERROR_OPEN_FAILED;
}
diff --git a/media/libmediaplayerservice/RemoteDisplay.cpp b/media/libmediaplayerservice/RemoteDisplay.cpp
index 20e6513..eb959b4 100644
--- a/media/libmediaplayerservice/RemoteDisplay.cpp
+++ b/media/libmediaplayerservice/RemoteDisplay.cpp
@@ -16,19 +16,23 @@
#include "RemoteDisplay.h"
-#include "ANetworkSession.h"
#include "source/WifiDisplaySource.h"
#include <media/IRemoteDisplayClient.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
namespace android {
RemoteDisplay::RemoteDisplay(
- const sp<IRemoteDisplayClient> &client, const char *iface)
+ const sp<IRemoteDisplayClient> &client,
+ const char *iface)
: mLooper(new ALooper),
- mNetSession(new ANetworkSession),
- mSource(new WifiDisplaySource(mNetSession, client)) {
+ mNetSession(new ANetworkSession) {
mLooper->setName("wfd_looper");
+
+ mSource = new WifiDisplaySource(mNetSession, client);
mLooper->registerHandler(mSource);
mNetSession->start();
@@ -50,6 +54,7 @@ status_t RemoteDisplay::resume() {
status_t RemoteDisplay::dispose() {
mSource->stop();
+ mSource.clear();
mLooper->stop();
mNetSession->stop();
diff --git a/media/libmediaplayerservice/RemoteDisplay.h b/media/libmediaplayerservice/RemoteDisplay.h
index bd8b684..82a0116 100644
--- a/media/libmediaplayerservice/RemoteDisplay.h
+++ b/media/libmediaplayerservice/RemoteDisplay.h
@@ -18,6 +18,7 @@
#define REMOTE_DISPLAY_H_
+#include <media/IMediaPlayerService.h>
#include <media/IRemoteDisplay.h>
#include <media/stagefright/foundation/ABase.h>
#include <utils/Errors.h>
@@ -31,7 +32,9 @@ struct IRemoteDisplayClient;
struct WifiDisplaySource;
struct RemoteDisplay : public BnRemoteDisplay {
- RemoteDisplay(const sp<IRemoteDisplayClient> &client, const char *iface);
+ RemoteDisplay(
+ const sp<IRemoteDisplayClient> &client,
+ const char *iface);
virtual status_t pause();
virtual status_t resume();
diff --git a/media/libmediaplayerservice/SharedLibrary.cpp b/media/libmediaplayerservice/SharedLibrary.cpp
index 178e15d..34db761 100644
--- a/media/libmediaplayerservice/SharedLibrary.cpp
+++ b/media/libmediaplayerservice/SharedLibrary.cpp
@@ -46,4 +46,10 @@ namespace android {
}
return dlsym(mLibHandle, symbol);
}
+
+ const char *SharedLibrary::lastError() const {
+ const char *error = dlerror();
+ return error ? error : "No errors or unknown error";
+ }
+
};
diff --git a/media/libmediaplayerservice/SharedLibrary.h b/media/libmediaplayerservice/SharedLibrary.h
index 5353642..88451a0 100644
--- a/media/libmediaplayerservice/SharedLibrary.h
+++ b/media/libmediaplayerservice/SharedLibrary.h
@@ -29,6 +29,7 @@ namespace android {
bool operator!() const;
void *lookup(const char *symbol) const;
+ const char *lastError() const;
private:
void *mLibHandle;
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index 655ee55..d8b35d7 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -20,7 +20,6 @@
#include "HTTPLiveSource.h"
-#include "ATSParser.h"
#include "AnotherPacketSource.h"
#include "LiveDataSource.h"
#include "LiveSession.h"
@@ -44,7 +43,8 @@ NuPlayer::HTTPLiveSource::HTTPLiveSource(
mUID(uid),
mFlags(0),
mFinalResult(OK),
- mOffset(0) {
+ mOffset(0),
+ mFetchSubtitleDataGeneration(0) {
if (headers) {
mExtraHeaders = *headers;
@@ -62,7 +62,10 @@ NuPlayer::HTTPLiveSource::HTTPLiveSource(
NuPlayer::HTTPLiveSource::~HTTPLiveSource() {
if (mLiveSession != NULL) {
mLiveSession->disconnect();
+ mLiveSession.clear();
+
mLiveLooper->stop();
+ mLiveLooper.clear();
}
}
@@ -76,128 +79,72 @@ void NuPlayer::HTTPLiveSource::prepareAsync() {
mLiveSession = new LiveSession(
notify,
(mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0,
- mUIDValid, mUID);
+ mUIDValid,
+ mUID);
mLiveLooper->registerHandler(mLiveSession);
- mLiveSession->connect(
+ mLiveSession->connectAsync(
mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
-
- mTSParser = new ATSParser;
}
void NuPlayer::HTTPLiveSource::start() {
}
-sp<MetaData> NuPlayer::HTTPLiveSource::getFormatMeta(bool audio) {
- ATSParser::SourceType type =
- audio ? ATSParser::AUDIO : ATSParser::VIDEO;
+sp<AMessage> NuPlayer::HTTPLiveSource::getFormat(bool audio) {
+ sp<AMessage> format;
+ status_t err = mLiveSession->getStreamFormat(
+ audio ? LiveSession::STREAMTYPE_AUDIO
+ : LiveSession::STREAMTYPE_VIDEO,
+ &format);
- sp<AnotherPacketSource> source =
- static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get());
-
- if (source == NULL) {
+ if (err != OK) {
return NULL;
}
- return source->getFormat();
+ return format;
}
status_t NuPlayer::HTTPLiveSource::feedMoreTSData() {
- if (mFinalResult != OK) {
- return mFinalResult;
- }
-
- sp<LiveDataSource> source =
- static_cast<LiveDataSource *>(mLiveSession->getDataSource().get());
-
- for (int32_t i = 0; i < 50; ++i) {
- char buffer[188];
- ssize_t n = source->readAtNonBlocking(mOffset, buffer, sizeof(buffer));
-
- if (n == -EWOULDBLOCK) {
- break;
- } else if (n < 0) {
- if (n != ERROR_END_OF_STREAM) {
- ALOGI("input data EOS reached, error %ld", n);
- } else {
- ALOGI("input data EOS reached.");
- }
- mTSParser->signalEOS(n);
- mFinalResult = n;
- break;
- } else {
- if (buffer[0] == 0x00) {
- // XXX legacy
-
- uint8_t type = buffer[1];
-
- sp<AMessage> extra = new AMessage;
-
- if (type & 2) {
- int64_t mediaTimeUs;
- memcpy(&mediaTimeUs, &buffer[2], sizeof(mediaTimeUs));
-
- extra->setInt64(IStreamListener::kKeyMediaTimeUs, mediaTimeUs);
- }
-
- mTSParser->signalDiscontinuity(
- ((type & 1) == 0)
- ? ATSParser::DISCONTINUITY_SEEK
- : ATSParser::DISCONTINUITY_FORMATCHANGE,
- extra);
- } else {
- status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer));
-
- if (err != OK) {
- ALOGE("TS Parser returned error %d", err);
- mTSParser->signalEOS(err);
- mFinalResult = err;
- break;
- }
- }
-
- mOffset += n;
- }
- }
-
return OK;
}
status_t NuPlayer::HTTPLiveSource::dequeueAccessUnit(
bool audio, sp<ABuffer> *accessUnit) {
- ATSParser::SourceType type =
- audio ? ATSParser::AUDIO : ATSParser::VIDEO;
-
- sp<AnotherPacketSource> source =
- static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get());
-
- if (source == NULL) {
- return -EWOULDBLOCK;
- }
-
- status_t finalResult;
- if (!source->hasBufferAvailable(&finalResult)) {
- return finalResult == OK ? -EWOULDBLOCK : finalResult;
- }
-
- return source->dequeueAccessUnit(accessUnit);
+ return mLiveSession->dequeueAccessUnit(
+ audio ? LiveSession::STREAMTYPE_AUDIO
+ : LiveSession::STREAMTYPE_VIDEO,
+ accessUnit);
}
status_t NuPlayer::HTTPLiveSource::getDuration(int64_t *durationUs) {
return mLiveSession->getDuration(durationUs);
}
-status_t NuPlayer::HTTPLiveSource::seekTo(int64_t seekTimeUs) {
- // We need to make sure we're not seeking until we have seen the very first
- // PTS timestamp in the whole stream (from the beginning of the stream).
- while (!mTSParser->PTSTimeDeltaEstablished() && feedMoreTSData() == OK) {
- usleep(100000);
+status_t NuPlayer::HTTPLiveSource::getTrackInfo(Parcel *reply) const {
+ return mLiveSession->getTrackInfo(reply);
+}
+
+status_t NuPlayer::HTTPLiveSource::selectTrack(size_t trackIndex, bool select) {
+ status_t err = mLiveSession->selectTrack(trackIndex, select);
+
+ if (err == OK) {
+ mFetchSubtitleDataGeneration++;
+ if (select) {
+ sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, id());
+ msg->setInt32("generation", mFetchSubtitleDataGeneration);
+ msg->post();
+ }
}
- mLiveSession->seekTo(seekTimeUs);
+ // LiveSession::selectTrack returns BAD_VALUE when selecting the currently
+ // selected track, or unselecting a non-selected track. In this case it's an
+ // no-op so we return OK.
+ return (err == OK || err == BAD_VALUE) ? OK : err;
+}
- return OK;
+status_t NuPlayer::HTTPLiveSource::seekTo(int64_t seekTimeUs) {
+ return mLiveSession->seekTo(seekTimeUs);
}
void NuPlayer::HTTPLiveSource::onMessageReceived(const sp<AMessage> &msg) {
@@ -208,6 +155,39 @@ void NuPlayer::HTTPLiveSource::onMessageReceived(const sp<AMessage> &msg) {
break;
}
+ case kWhatFetchSubtitleData:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mFetchSubtitleDataGeneration) {
+ // stale
+ break;
+ }
+
+ sp<ABuffer> buffer;
+ if (mLiveSession->dequeueAccessUnit(
+ LiveSession::STREAMTYPE_SUBTITLES, &buffer) == OK) {
+ sp<AMessage> notify = dupNotify();
+ notify->setInt32("what", kWhatSubtitleData);
+ notify->setBuffer("buffer", buffer);
+ notify->post();
+
+ int64_t timeUs, baseUs, durationUs, delayUs;
+ CHECK(buffer->meta()->findInt64("baseUs", &baseUs));
+ CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+ CHECK(buffer->meta()->findInt64("durationUs", &durationUs));
+ delayUs = baseUs + timeUs - ALooper::GetNowUs();
+
+ msg->post(delayUs > 0ll ? delayUs : 0ll);
+ } else {
+ // try again in 1 second
+ msg->post(1000000ll);
+ }
+
+ break;
+ }
+
default:
Source::onMessageReceived(msg);
break;
@@ -249,6 +229,32 @@ void NuPlayer::HTTPLiveSource::onSessionNotify(const sp<AMessage> &msg) {
break;
}
+ case LiveSession::kWhatStreamsChanged:
+ {
+ uint32_t changedMask;
+ CHECK(msg->findInt32(
+ "changedMask", (int32_t *)&changedMask));
+
+ bool audio = changedMask & LiveSession::STREAMTYPE_AUDIO;
+ bool video = changedMask & LiveSession::STREAMTYPE_VIDEO;
+
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ sp<AMessage> notify = dupNotify();
+ notify->setInt32("what", kWhatQueueDecoderShutdown);
+ notify->setInt32("audio", audio);
+ notify->setInt32("video", video);
+ notify->setMessage("reply", reply);
+ notify->post();
+ break;
+ }
+
+ case LiveSession::kWhatError:
+ {
+ break;
+ }
+
default:
TRESPASS();
}
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
index 067d1da..bcc3f8b 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
@@ -23,7 +23,6 @@
namespace android {
-struct ATSParser;
struct LiveSession;
struct NuPlayer::HTTPLiveSource : public NuPlayer::Source {
@@ -37,18 +36,18 @@ struct NuPlayer::HTTPLiveSource : public NuPlayer::Source {
virtual void prepareAsync();
virtual void start();
- virtual status_t feedMoreTSData();
-
virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+ virtual sp<AMessage> getFormat(bool audio);
+ virtual status_t feedMoreTSData();
virtual status_t getDuration(int64_t *durationUs);
+ virtual status_t getTrackInfo(Parcel *reply) const;
+ virtual status_t selectTrack(size_t trackIndex, bool select);
virtual status_t seekTo(int64_t seekTimeUs);
protected:
virtual ~HTTPLiveSource();
- virtual sp<MetaData> getFormatMeta(bool audio);
-
virtual void onMessageReceived(const sp<AMessage> &msg);
private:
@@ -59,6 +58,7 @@ private:
enum {
kWhatSessionNotify,
+ kWhatFetchSubtitleData,
};
AString mURL;
@@ -70,7 +70,7 @@ private:
off64_t mOffset;
sp<ALooper> mLiveLooper;
sp<LiveSession> mLiveSession;
- sp<ATSParser> mTSParser;
+ int32_t mFetchSubtitleDataGeneration;
void onSessionNotify(const sp<AMessage> &msg);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index b89b1c8..750287f 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -89,6 +89,38 @@ private:
DISALLOW_EVIL_CONSTRUCTORS(SetSurfaceAction);
};
+struct NuPlayer::ShutdownDecoderAction : public Action {
+ ShutdownDecoderAction(bool audio, bool video)
+ : mAudio(audio),
+ mVideo(video) {
+ }
+
+ virtual void execute(NuPlayer *player) {
+ player->performDecoderShutdown(mAudio, mVideo);
+ }
+
+private:
+ bool mAudio;
+ bool mVideo;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ShutdownDecoderAction);
+};
+
+struct NuPlayer::PostMessageAction : public Action {
+ PostMessageAction(const sp<AMessage> &msg)
+ : mMessage(msg) {
+ }
+
+ virtual void execute(NuPlayer *) {
+ mMessage->post();
+ }
+
+private:
+ sp<AMessage> mMessage;
+
+ DISALLOW_EVIL_CONSTRUCTORS(PostMessageAction);
+};
+
// Use this if there's no state necessary to save in order to execute
// the action.
struct NuPlayer::SimpleAction : public Action {
@@ -308,6 +340,46 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
break;
}
+ case kWhatGetTrackInfo:
+ {
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ status_t err = INVALID_OPERATION;
+ if (mSource != NULL) {
+ Parcel* reply;
+ CHECK(msg->findPointer("reply", (void**)&reply));
+ err = mSource->getTrackInfo(reply);
+ }
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatSelectTrack:
+ {
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ status_t err = INVALID_OPERATION;
+ if (mSource != NULL) {
+ size_t trackIndex;
+ int32_t select;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+ CHECK(msg->findInt32("select", &select));
+ err = mSource->selectTrack(trackIndex, select);
+ }
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+
+ response->postReply(replyID);
+ break;
+ }
+
case kWhatPollDuration:
{
int32_t generation;
@@ -335,7 +407,8 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
ALOGV("kWhatSetVideoNativeWindow");
mDeferredActions.push_back(
- new SimpleAction(&NuPlayer::performDecoderShutdown));
+ new ShutdownDecoderAction(
+ false /* audio */, true /* video */));
sp<RefBase> obj;
CHECK(msg->findObject("native-window", &obj));
@@ -698,6 +771,9 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
ALOGV("renderer %s flush completed.", audio ? "audio" : "video");
} else if (what == Renderer::kWhatVideoRenderingStart) {
notifyListener(MEDIA_INFO, MEDIA_INFO_RENDERING_START, 0);
+ } else if (what == Renderer::kWhatMediaRenderingStart) {
+ ALOGV("media rendering started");
+ notifyListener(MEDIA_STARTED, 0, 0);
}
break;
}
@@ -712,7 +788,8 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
ALOGV("kWhatReset");
mDeferredActions.push_back(
- new SimpleAction(&NuPlayer::performDecoderShutdown));
+ new ShutdownDecoderAction(
+ true /* audio */, true /* video */));
mDeferredActions.push_back(
new SimpleAction(&NuPlayer::performReset));
@@ -1008,7 +1085,7 @@ void NuPlayer::renderBuffer(bool audio, const sp<AMessage> &msg) {
mRenderer->queueBuffer(audio, buffer, reply);
}
-void NuPlayer::notifyListener(int msg, int ext1, int ext2) {
+void NuPlayer::notifyListener(int msg, int ext1, int ext2, const Parcel *in) {
if (mDriver == NULL) {
return;
}
@@ -1019,10 +1096,13 @@ void NuPlayer::notifyListener(int msg, int ext1, int ext2) {
return;
}
- driver->notifyListener(msg, ext1, ext2);
+ driver->notifyListener(msg, ext1, ext2, in);
}
void NuPlayer::flushDecoder(bool audio, bool needShutdown) {
+ ALOGV("[%s] flushDecoder needShutdown=%d",
+ audio ? "audio" : "video", needShutdown);
+
if ((audio && mAudioDecoder == NULL) || (!audio && mVideoDecoder == NULL)) {
ALOGI("flushDecoder %s without decoder present",
audio ? "audio" : "video");
@@ -1092,6 +1172,26 @@ status_t NuPlayer::setVideoScalingMode(int32_t mode) {
return OK;
}
+status_t NuPlayer::getTrackInfo(Parcel* reply) const {
+ sp<AMessage> msg = new AMessage(kWhatGetTrackInfo, id());
+ msg->setPointer("reply", reply);
+
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+ return err;
+}
+
+status_t NuPlayer::selectTrack(size_t trackIndex, bool select) {
+ sp<AMessage> msg = new AMessage(kWhatSelectTrack, id());
+ msg->setSize("trackIndex", trackIndex);
+ msg->setInt32("select", select);
+
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ return err;
+}
+
void NuPlayer::schedulePollDuration() {
sp<AMessage> msg = new AMessage(kWhatPollDuration, id());
msg->setInt32("generation", mPollDurationGeneration);
@@ -1173,20 +1273,29 @@ void NuPlayer::performDecoderFlush() {
}
}
-void NuPlayer::performDecoderShutdown() {
- ALOGV("performDecoderShutdown");
+void NuPlayer::performDecoderShutdown(bool audio, bool video) {
+ ALOGV("performDecoderShutdown audio=%d, video=%d", audio, video);
- if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
+ if ((!audio || mAudioDecoder == NULL)
+ && (!video || mVideoDecoder == NULL)) {
return;
}
mTimeDiscontinuityPending = true;
- if (mAudioDecoder != NULL) {
+ if (mFlushingAudio == NONE && (!audio || mAudioDecoder == NULL)) {
+ mFlushingAudio = FLUSHED;
+ }
+
+ if (mFlushingVideo == NONE && (!video || mVideoDecoder == NULL)) {
+ mFlushingVideo = FLUSHED;
+ }
+
+ if (audio && mAudioDecoder != NULL) {
flushDecoder(true /* audio */, true /* needShutdown */);
}
- if (mVideoDecoder != NULL) {
+ if (video && mVideoDecoder != NULL) {
flushDecoder(false /* audio */, true /* needShutdown */);
}
}
@@ -1287,6 +1396,11 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) {
uint32_t flags;
CHECK(msg->findInt32("flags", (int32_t *)&flags));
+ sp<NuPlayerDriver> driver = mDriver.promote();
+ if (driver != NULL) {
+ driver->notifyFlagsChanged(flags);
+ }
+
if ((mSourceFlags & Source::FLAG_DYNAMIC_DURATION)
&& (!(flags & Source::FLAG_DYNAMIC_DURATION))) {
cancelPollDuration();
@@ -1322,6 +1436,42 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) {
break;
}
+ case Source::kWhatSubtitleData:
+ {
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
+
+ int32_t trackIndex;
+ int64_t timeUs, durationUs;
+ CHECK(buffer->meta()->findInt32("trackIndex", &trackIndex));
+ CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+ CHECK(buffer->meta()->findInt64("durationUs", &durationUs));
+
+ Parcel in;
+ in.writeInt32(trackIndex);
+ in.writeInt64(timeUs);
+ in.writeInt64(durationUs);
+ in.writeInt32(buffer->size());
+ in.writeInt32(buffer->size());
+ in.write(buffer->data(), buffer->size());
+
+ notifyListener(MEDIA_SUBTITLE_DATA, 0, 0, &in);
+ break;
+ }
+
+ case Source::kWhatQueueDecoderShutdown:
+ {
+ int32_t audio, video;
+ CHECK(msg->findInt32("audio", &audio));
+ CHECK(msg->findInt32("video", &video));
+
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ queueDecoderShutdown(audio, video, reply);
+ break;
+ }
+
default:
TRESPASS();
}
@@ -1355,4 +1505,19 @@ void NuPlayer::Source::onMessageReceived(const sp<AMessage> &msg) {
TRESPASS();
}
+void NuPlayer::queueDecoderShutdown(
+ bool audio, bool video, const sp<AMessage> &reply) {
+ ALOGI("queueDecoderShutdown audio=%d, video=%d", audio, video);
+
+ mDeferredActions.push_back(
+ new ShutdownDecoderAction(audio, video));
+
+ mDeferredActions.push_back(
+ new SimpleAction(&NuPlayer::performScanSources));
+
+ mDeferredActions.push_back(new PostMessageAction(reply));
+
+ processDeferredActions();
+}
+
} // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 50d0462..13350f3 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -60,6 +60,8 @@ struct NuPlayer : public AHandler {
void seekToAsync(int64_t seekTimeUs);
status_t setVideoScalingMode(int32_t mode);
+ status_t getTrackInfo(Parcel* reply) const;
+ status_t selectTrack(size_t trackIndex, bool select);
protected:
virtual ~NuPlayer();
@@ -80,6 +82,8 @@ private:
struct Action;
struct SeekAction;
struct SetSurfaceAction;
+ struct ShutdownDecoderAction;
+ struct PostMessageAction;
struct SimpleAction;
enum {
@@ -99,6 +103,8 @@ private:
kWhatResume = 'rsme',
kWhatPollDuration = 'polD',
kWhatSourceNotify = 'srcN',
+ kWhatGetTrackInfo = 'gTrI',
+ kWhatSelectTrack = 'selT',
};
wp<NuPlayerDriver> mDriver;
@@ -155,7 +161,7 @@ private:
status_t feedDecoderInputData(bool audio, const sp<AMessage> &msg);
void renderBuffer(bool audio, const sp<AMessage> &msg);
- void notifyListener(int msg, int ext1, int ext2);
+ void notifyListener(int msg, int ext1, int ext2, const Parcel *in = NULL);
void finishFlushIfPossible();
@@ -172,13 +178,16 @@ private:
void performSeek(int64_t seekTimeUs);
void performDecoderFlush();
- void performDecoderShutdown();
+ void performDecoderShutdown(bool audio, bool video);
void performReset();
void performScanSources();
void performSetSurface(const sp<NativeWindowWrapper> &wrapper);
void onSourceNotify(const sp<AMessage> &msg);
+ void queueDecoderShutdown(
+ bool audio, bool video, const sp<AMessage> &reply);
+
DISALLOW_EVIL_CONSTRUCTORS(NuPlayer);
};
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 68b9623..47834fd 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -255,6 +255,7 @@ status_t NuPlayerDriver::pause() {
return OK;
case STATE_RUNNING:
+ notifyListener(MEDIA_PAUSED);
mPlayer->pause();
break;
@@ -287,6 +288,8 @@ status_t NuPlayerDriver::seekTo(int msec) {
case STATE_PAUSED:
{
mAtEOS = false;
+ // seeks can take a while, so we essentially paused
+ notifyListener(MEDIA_PAUSED);
mPlayer->seekToAsync(seekTimeUs);
break;
}
@@ -345,6 +348,8 @@ status_t NuPlayerDriver::reset() {
break;
}
+ notifyListener(MEDIA_STOPPED);
+
mState = STATE_RESET_IN_PROGRESS;
mPlayer->resetAsync();
@@ -387,6 +392,23 @@ status_t NuPlayerDriver::invoke(const Parcel &request, Parcel *reply) {
return mPlayer->setVideoScalingMode(mode);
}
+ case INVOKE_ID_GET_TRACK_INFO:
+ {
+ return mPlayer->getTrackInfo(reply);
+ }
+
+ case INVOKE_ID_SELECT_TRACK:
+ {
+ int trackIndex = request.readInt32();
+ return mPlayer->selectTrack(trackIndex, true /* select */);
+ }
+
+ case INVOKE_ID_UNSELECT_TRACK:
+ {
+ int trackIndex = request.readInt32();
+ return mPlayer->selectTrack(trackIndex, false /* select */);
+ }
+
default:
{
return INVALID_OPERATION;
@@ -490,12 +512,13 @@ status_t NuPlayerDriver::dump(int fd, const Vector<String16> &args) const {
return OK;
}
-void NuPlayerDriver::notifyListener(int msg, int ext1, int ext2) {
+void NuPlayerDriver::notifyListener(
+ int msg, int ext1, int ext2, const Parcel *in) {
if (msg == MEDIA_PLAYBACK_COMPLETE || msg == MEDIA_ERROR) {
mAtEOS = true;
}
- sendEvent(msg, ext1, ext2);
+ sendEvent(msg, ext1, ext2, in);
}
void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) {
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index 5df0cfb..99f72a6 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -69,7 +69,7 @@ struct NuPlayerDriver : public MediaPlayerInterface {
void notifyPosition(int64_t positionUs);
void notifySeekComplete();
void notifyFrameStats(int64_t numFramesTotal, int64_t numFramesDropped);
- void notifyListener(int msg, int ext1 = 0, int ext2 = 0);
+ void notifyListener(int msg, int ext1 = 0, int ext2 = 0, const Parcel *in = NULL);
void notifyFlagsChanged(uint32_t flags);
protected:
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 404b56f..3b2784b 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -50,6 +50,8 @@ NuPlayer::Renderer::Renderer(
mSyncQueues(false),
mPaused(false),
mVideoRenderingStarted(false),
+ mVideoRenderingStartGeneration(0),
+ mAudioRenderingStartGeneration(0),
mLastPositionUpdateUs(-1ll),
mVideoLateByUs(0ll) {
}
@@ -95,11 +97,11 @@ void NuPlayer::Renderer::flush(bool audio) {
}
void NuPlayer::Renderer::signalTimeDiscontinuity() {
- CHECK(mAudioQueue.empty());
- CHECK(mVideoQueue.empty());
+ // CHECK(mAudioQueue.empty());
+ // CHECK(mVideoQueue.empty());
mAnchorTimeMediaUs = -1;
mAnchorTimeRealUs = -1;
- mSyncQueues = mHasAudio && mHasVideo;
+ mSyncQueues = false;
}
void NuPlayer::Renderer::pause() {
@@ -220,6 +222,23 @@ void NuPlayer::Renderer::signalAudioSinkChanged() {
(new AMessage(kWhatAudioSinkChanged, id()))->post();
}
+void NuPlayer::Renderer::prepareForMediaRenderingStart() {
+ mAudioRenderingStartGeneration = mAudioQueueGeneration;
+ mVideoRenderingStartGeneration = mVideoQueueGeneration;
+}
+
+void NuPlayer::Renderer::notifyIfMediaRenderingStarted() {
+ if (mVideoRenderingStartGeneration == mVideoQueueGeneration &&
+ mAudioRenderingStartGeneration == mAudioQueueGeneration) {
+ mVideoRenderingStartGeneration = -1;
+ mAudioRenderingStartGeneration = -1;
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatMediaRenderingStart);
+ notify->post();
+ }
+}
+
bool NuPlayer::Renderer::onDrainAudioQueue() {
uint32_t numFramesPlayed;
if (mAudioSink->getPosition(&numFramesPlayed) != OK) {
@@ -299,6 +318,8 @@ bool NuPlayer::Renderer::onDrainAudioQueue() {
numBytesAvailableToWrite -= copy;
size_t copiedFrames = copy / mAudioSink->frameSize();
mNumFramesWritten += copiedFrames;
+
+ notifyIfMediaRenderingStarted();
}
notifyPosition();
@@ -405,6 +426,8 @@ void NuPlayer::Renderer::onDrainVideoQueue() {
notifyVideoRenderingStart();
}
+ notifyIfMediaRenderingStarted();
+
notifyPosition();
}
@@ -552,6 +575,7 @@ void NuPlayer::Renderer::onFlush(const sp<AMessage> &msg) {
// is flushed.
syncQueuesDone();
+ ALOGV("flushing %s", audio ? "audio" : "video");
if (audio) {
flushQueue(&mAudioQueue);
@@ -560,6 +584,8 @@ void NuPlayer::Renderer::onFlush(const sp<AMessage> &msg) {
mDrainAudioQueuePending = false;
++mAudioQueueGeneration;
+
+ prepareForMediaRenderingStart();
} else {
flushQueue(&mVideoQueue);
@@ -568,6 +594,8 @@ void NuPlayer::Renderer::onFlush(const sp<AMessage> &msg) {
mDrainVideoQueuePending = false;
++mVideoQueueGeneration;
+
+ prepareForMediaRenderingStart();
}
notifyFlushComplete(audio);
@@ -658,6 +686,8 @@ void NuPlayer::Renderer::onPause() {
mDrainVideoQueuePending = false;
++mVideoQueueGeneration;
+ prepareForMediaRenderingStart();
+
if (mHasAudio) {
mAudioSink->pause();
}
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
index c9796e2..94a05ea 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
@@ -53,6 +53,7 @@ struct NuPlayer::Renderer : public AHandler {
kWhatFlushComplete = 'fluC',
kWhatPosition = 'posi',
kWhatVideoRenderingStart = 'vdrd',
+ kWhatMediaRenderingStart = 'mdrd',
};
protected:
@@ -106,6 +107,8 @@ private:
bool mPaused;
bool mVideoRenderingStarted;
+ int32_t mVideoRenderingStartGeneration;
+ int32_t mAudioRenderingStartGeneration;
int64_t mLastPositionUpdateUs;
int64_t mVideoLateByUs;
@@ -116,6 +119,9 @@ private:
void onDrainVideoQueue();
void postDrainVideoQueue();
+ void prepareForMediaRenderingStart();
+ void notifyIfMediaRenderingStarted();
+
void onQueueBuffer(const sp<AMessage> &msg);
void onQueueEOS(const sp<AMessage> &msg);
void onFlush(const sp<AMessage> &msg);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
index 1cbf575..e50533a 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
@@ -42,6 +42,8 @@ struct NuPlayer::Source : public AHandler {
kWhatVideoSizeChanged,
kWhatBufferingStart,
kWhatBufferingEnd,
+ kWhatSubtitleData,
+ kWhatQueueDecoderShutdown,
};
// The provides message is used to notify the player about various
@@ -70,6 +72,14 @@ struct NuPlayer::Source : public AHandler {
return INVALID_OPERATION;
}
+ virtual status_t getTrackInfo(Parcel* reply) const {
+ return INVALID_OPERATION;
+ }
+
+ virtual status_t selectTrack(size_t trackIndex, bool select) {
+ return INVALID_OPERATION;
+ }
+
virtual status_t seekTo(int64_t seekTimeUs) {
return INVALID_OPERATION;
}
diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
index 3385a19..18cf6d1 100644
--- a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
@@ -358,11 +358,10 @@ void NuPlayer::RTSPSource::onMessageReceived(const sp<AMessage> &msg) {
uint32_t flags = 0;
if (mHandler->isSeekable()) {
- flags = FLAG_CAN_PAUSE | FLAG_CAN_SEEK;
-
- // Seeking 10secs forward or backward is a very expensive
- // operation for rtsp, so let's not enable that.
- // The user can always use the seek bar.
+ flags = FLAG_CAN_PAUSE
+ | FLAG_CAN_SEEK
+ | FLAG_CAN_SEEK_BACKWARD
+ | FLAG_CAN_SEEK_FORWARD;
}
notifyFlagsChanged(flags);
diff --git a/media/libnbaio/Android.mk b/media/libnbaio/Android.mk
index 5d00d15..69c75b8 100644
--- a/media/libnbaio/Android.mk
+++ b/media/libnbaio/Android.mk
@@ -31,6 +31,9 @@ LOCAL_SHARED_LIBRARIES := \
libcommon_time_client \
libcutils \
libutils \
- liblog
+ liblog \
+ libmedia
+# This dependency on libmedia is for SingleStateQueueInstantiations.
+# Consider a separate a library for SingleStateQueueInstantiations.
include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libnbaio/AudioStreamOutSink.cpp b/media/libnbaio/AudioStreamOutSink.cpp
index 6f525e5..e4341d7 100644
--- a/media/libnbaio/AudioStreamOutSink.cpp
+++ b/media/libnbaio/AudioStreamOutSink.cpp
@@ -79,4 +79,19 @@ status_t AudioStreamOutSink::getNextWriteTimestamp(int64_t *timestamp) {
return mStream->get_next_write_timestamp(mStream, timestamp);
}
+status_t AudioStreamOutSink::getTimestamp(AudioTimestamp& timestamp)
+{
+ if (mStream->get_presentation_position == NULL) {
+ return INVALID_OPERATION;
+ }
+ // FIXME position64 won't be needed after AudioTimestamp.mPosition is changed to uint64_t
+ uint64_t position64;
+ int ok = mStream->get_presentation_position(mStream, &position64, &timestamp.mTime);
+ if (ok != 0) {
+ return INVALID_OPERATION;
+ }
+ timestamp.mPosition = position64;
+ return OK;
+}
+
} // namespace android
diff --git a/media/libnbaio/MonoPipe.cpp b/media/libnbaio/MonoPipe.cpp
index e8d3d9b..de0ad28 100644
--- a/media/libnbaio/MonoPipe.cpp
+++ b/media/libnbaio/MonoPipe.cpp
@@ -42,7 +42,10 @@ MonoPipe::MonoPipe(size_t reqFrames, NBAIO_Format format, bool writeCanBlock) :
// mWriteTs
mSetpoint((reqFrames * 11) / 16),
mWriteCanBlock(writeCanBlock),
- mIsShutdown(false)
+ mIsShutdown(false),
+ // mTimestampShared
+ mTimestampMutator(&mTimestampShared),
+ mTimestampObserver(&mTimestampShared)
{
CCHelper tmpHelper;
status_t res;
@@ -310,4 +313,12 @@ bool MonoPipe::isShutdown()
return mIsShutdown;
}
+status_t MonoPipe::getTimestamp(AudioTimestamp& timestamp)
+{
+ if (mTimestampObserver.poll(timestamp)) {
+ return OK;
+ }
+ return INVALID_OPERATION;
+}
+
} // namespace android
diff --git a/media/libnbaio/MonoPipeReader.cpp b/media/libnbaio/MonoPipeReader.cpp
index 394f6ac..851341a 100644
--- a/media/libnbaio/MonoPipeReader.cpp
+++ b/media/libnbaio/MonoPipeReader.cpp
@@ -86,4 +86,9 @@ ssize_t MonoPipeReader::read(void *buffer, size_t count, int64_t readPTS)
return red;
}
+void MonoPipeReader::onTimestamp(const AudioTimestamp& timestamp)
+{
+ mPipe->mTimestampMutator.push(timestamp);
+}
+
} // namespace android
diff --git a/media/libnbaio/SourceAudioBufferProvider.cpp b/media/libnbaio/SourceAudioBufferProvider.cpp
index d11a86c..062fa0f 100644
--- a/media/libnbaio/SourceAudioBufferProvider.cpp
+++ b/media/libnbaio/SourceAudioBufferProvider.cpp
@@ -25,7 +25,7 @@ namespace android {
SourceAudioBufferProvider::SourceAudioBufferProvider(const sp<NBAIO_Source>& source) :
mSource(source),
// mFrameBitShiftFormat below
- mAllocated(NULL), mSize(0), mOffset(0), mRemaining(0), mGetCount(0)
+ mAllocated(NULL), mSize(0), mOffset(0), mRemaining(0), mGetCount(0), mFramesReleased(0)
{
ALOG_ASSERT(source != 0);
@@ -90,6 +90,7 @@ void SourceAudioBufferProvider::releaseBuffer(Buffer *buffer)
(mOffset + mRemaining <= mSize));
mOffset += buffer->frameCount;
mRemaining -= buffer->frameCount;
+ mFramesReleased += buffer->frameCount;
buffer->raw = NULL;
buffer->frameCount = 0;
mGetCount = 0;
@@ -101,4 +102,14 @@ size_t SourceAudioBufferProvider::framesReady() const
return avail < 0 ? 0 : (size_t) avail;
}
+size_t SourceAudioBufferProvider::framesReleased() const
+{
+ return mFramesReleased;
+}
+
+void SourceAudioBufferProvider::onTimestamp(const AudioTimestamp& timestamp)
+{
+ mSource->onTimestamp(timestamp);
+}
+
} // namespace android
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;
-}