summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
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.cpp474
-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.cpp106
-rw-r--r--media/libeffects/loudness/dsp/core/dynamic_range_compression.h116
-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.cpp338
-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.cpp997
-rw-r--r--media/libmedia/AudioSystem.cpp53
-rw-r--r--media/libmedia/AudioTrack.cpp1447
-rw-r--r--media/libmedia/AudioTrackShared.cpp868
-rw-r--r--media/libmedia/IAudioFlinger.cpp115
-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.cpp46
-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.cpp62
-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.cpp68
-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.cpp363
-rw-r--r--media/libstagefright/Android.mk5
-rw-r--r--media/libstagefright/AudioPlayer.cpp377
-rw-r--r--media/libstagefright/AudioSource.cpp9
-rw-r--r--media/libstagefright/AwesomePlayer.cpp270
-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.cpp27
-rw-r--r--media/libstagefright/OMXCodec.cpp21
-rw-r--r--media/libstagefright/SurfaceMediaSource.cpp41
-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.cpp198
-rw-r--r--media/libstagefright/codecs/on2/enc/SoftVPXEncoder.h22
-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.h20
-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.h11
-rw-r--r--media/libstagefright/include/OMXNodeInstance.h10
-rw-r--r--media/libstagefright/include/SoftVideoDecoderOMXComponent.h93
-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.cpp16
-rw-r--r--media/libstagefright/omx/OMXNodeInstance.cpp91
-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.h6
-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.mk72
-rw-r--r--media/libstagefright/wifi-display/MediaReceiver.cpp328
-rw-r--r--media/libstagefright/wifi-display/MediaReceiver.h111
-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/SNTPClient.cpp174
-rw-r--r--media/libstagefright/wifi-display/SNTPClient.h62
-rw-r--r--media/libstagefright/wifi-display/TimeSyncer.cpp337
-rw-r--r--media/libstagefright/wifi-display/TimeSyncer.h109
-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/nettest.cpp400
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPAssembler.cpp328
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPAssembler.h92
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPReceiver.cpp1152
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPReceiver.h125
-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/rtptest.cpp565
-rw-r--r--media/libstagefright/wifi-display/sink/DirectRenderer.cpp653
-rw-r--r--media/libstagefright/wifi-display/sink/DirectRenderer.h87
-rw-r--r--media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp917
-rw-r--r--media/libstagefright/wifi-display/sink/WifiDisplaySink.h195
-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.cpp54
-rw-r--r--media/libstagefright/wifi-display/source/WifiDisplaySource.h7
-rw-r--r--media/libstagefright/wifi-display/udptest.cpp116
-rw-r--r--media/libstagefright/wifi-display/wfd.cpp164
193 files changed, 18643 insertions, 3779 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..dfc25db
--- /dev/null
+++ b/media/libeffects/loudness/EffectLoudnessEnhancer.cpp
@@ -0,0 +1,474 @@
+/*
+ * 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* mCompressorL;
+ le_fx::AdaptiveDynamicRangeCompression* mCompressorR;
+};
+
+//
+//--- Local functions (not directly used by effect interface)
+//
+
+void LE_reset(LoudnessEnhancerContext *pContext)
+{
+ ALOGV(" > LE_reset(%p)", pContext);
+
+ if ((pContext->mCompressorL != NULL) && (pContext->mCompressorR != 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->mCompressorL->Initialize(targetAmp, pContext->mConfig.inputCfg.samplingRate);
+ pContext->mCompressorR->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->mCompressorL == NULL) {
+ pContext->mCompressorL = new le_fx::AdaptiveDynamicRangeCompression();
+ pContext->mCompressorL->Initialize(targetAmp, pContext->mConfig.inputCfg.samplingRate);
+ }
+ if (pContext->mCompressorR == NULL) {
+ pContext->mCompressorR = new le_fx::AdaptiveDynamicRangeCompression();
+ pContext->mCompressorR->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->mCompressorL = NULL;
+ pContext->mCompressorR = 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->mCompressorL != NULL) {
+ delete pContext->mCompressorL;
+ pContext->mCompressorL = NULL;
+ }
+ if (pContext->mCompressorR != NULL) {
+ delete pContext->mCompressorR;
+ pContext->mCompressorR = 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);
+ for (inIdx = 0 ; inIdx < inBuffer->frameCount ; inIdx++) {
+ inBuffer->s16[2*inIdx] = pContext->mCompressorL->Compress(
+ inputAmp * (float)inBuffer->s16[2*inIdx]);
+ inBuffer->s16[2*inIdx +1] = pContext->mCompressorR->Compress(
+ inputAmp * (float)inBuffer->s16[2*inIdx +1]);
+ }
+
+ 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..fed8c2a
--- /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);
+ ALOGE("set_knee_threshold_via_target_gain: decibel =%.3f", 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..2bbd043
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/dynamic_range_compression.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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;
+}
+
+} // 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..4c015df
--- /dev/null
+++ b/media/libeffects/loudness/dsp/core/dynamic_range_compression.h
@@ -0,0 +1,116 @@
+/*
+ * 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);
+
+ // 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..b3304b7
--- /dev/null
+++ b/media/libeffects/proxy/EffectProxy.cpp
@@ -0,0 +1,338 @@
+/*
+ * 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
+const effect_descriptor_t gProxyDescriptor = {
+ EFFECT_UUID_INITIALIZER, // type
+ EFFECT_UUID_INITIALIZER, // 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 e7eccf1..dc403ab 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..c5a7777 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,15 +430,44 @@ 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);
@@ -463,6 +476,7 @@ status_t AudioRecord::openRecord_l(
if (record == 0) {
ALOGE("AudioFlinger could not create record track, status: %d", status);
+ AudioSystem::releaseInput(input);
return status;
}
sp<IMemory> iMem = record->getCblk();
@@ -470,133 +484,158 @@ status_t AudioRecord::openRecord_l(
ALOGE("Could not get control block");
return NO_INIT;
}
- mAudioRecord.clear();
+ 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());
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);
- audioBuffer->frameCount = framesReq;
- audioBuffer->size = framesReq * mFrameSize;
- audioBuffer->raw = mProxy->buffer(u);
- active = mActive;
- return active ? status_t(NO_ERROR) : status_t(STOPPED);
+ } while ((status == DEAD_OBJECT) && (tryCounter-- > 0));
+
+ 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 +644,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 +978,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 +1029,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..754a4e3 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;
}
@@ -490,18 +544,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 +558,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 +596,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 +643,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 +658,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 +686,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 +805,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 +815,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 +853,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 +869,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 +898,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 +914,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 +942,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 +958,7 @@ status_t AudioTrack::createTrack_l(
output,
tid,
&mSessionId,
+ mName,
&status);
if (track == 0) {
@@ -881,6 +970,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 +992,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 +1045,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);
+}
- audioBuffer->frameCount = 0;
- audioBuffer->size = 0;
+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;
- size_t framesAvail = mProxy->framesAvailable();
+ Proxy::Buffer buffer;
+ status_t status = NO_ERROR;
- cblk->lock.lock();
- if (cblk->flags & CBLK_INVALID) {
- goto create_new_track;
- }
- cblk->lock.unlock();
+ static const int32_t kMaxTries = 5;
+ int32_t tryCounter = kMaxTries;
- 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);
+ 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;
+
+ { // 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;
- if (cblk->flags & CBLK_INVALID) {
- goto create_new_track;
+ // Keep the extra references
+ proxy = mProxy;
+ iMem = mCblkMemory;
+
+ 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 +1202,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 +1257,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 +1297,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 +1315,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 +1340,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);
+
+ sp<AudioTrackClientProxy> proxy = mProxy;
+ sp<IMemory> iMem = mCblkMemory;
- // 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;
+ 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 +1573,173 @@ 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
+ size_t position = mProxy->getPosition();
+ 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 +1756,33 @@ 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)
{
}
@@ -1506,18 +1799,46 @@ bool AudioTrack::AudioTrackThread::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 AudioTrack::AudioTrackThread::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 AudioTrack::AudioTrackThread::pause()
@@ -1529,10 +1850,18 @@ void AudioTrack::AudioTrackThread::pause()
void AudioTrack::AudioTrackThread::resume()
{
AutoMutex _l(mMyLock);
- if (mPaused) {
+ 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..4fd92b2 100644
--- a/media/libmedia/AudioTrackShared.cpp
+++ b/media/libmedia/AudioTrackShared.cpp
@@ -19,178 +19,820 @@
#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)
{
- ALOGV("stepuser %08x %08x %d", user, server, stepCount);
+}
+
+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
- 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 ClientProxy::obtainBuffer(Buffer* buffer, const struct timespec *requested,
+ struct timespec *elapsed)
+{
+ 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
+
+ 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;
-
- // 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);
+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;
+}
- return u;
+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);
+ }
}
-bool audio_track_cblk_t::stepServer(size_t stepCount, size_t frameCount, bool isOut)
+void ClientProxy::binderDied()
{
- ALOGV("stepserver %08x %08x %d", user, server, stepCount);
+ 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);
+ }
+}
- if (!tryLock()) {
- ALOGW("stepServer() could not lock cblk");
- return false;
+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);
}
+}
+
+size_t ClientProxy::getMisalignment()
+{
+ audio_track_cblk_t* cblk = mCblk;
+ return (mFrameCountP2 - (mIsOut ? cblk->u.mStreaming.mRear : cblk->u.mStreaming.mFront)) &
+ (mFrameCountP2 - 1);
+}
- uint32_t s = server;
- bool flushed = (s == user);
+// ---------------------------------------------------------------------------
- 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;
+void AudioTrackClientProxy::flush()
+{
+ mCblk->u.mStreaming.mFlush++;
+}
+
+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;
+}
+
+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;
+ }
+ 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;
}
- // 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;
+ 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;
+ }
}
}
- 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;
+end:
+ if (requested == NULL) {
+ requested = &kNonBlocking;
+ }
+ return status;
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+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);
}
- server = s;
+ mCblk->mServer += stepCount;
- if (!(flags & CBLK_INVALID)) {
- cv.signal();
+ size_t half = mFrameCount / 2;
+ if (half == 0) {
+ half = 1;
}
- lock.unlock();
- return true;
+ size_t minimum = cblk->mMinimum;
+ if (minimum == 0) {
+ minimum = mIsOut ? half : 1;
+ } else if (minimum > half) {
+ minimum = half;
+ }
+ // 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;
}
-uint32_t audio_track_cblk_t::framesAvailable(size_t frameCount, bool isOut)
+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;
+}
+
+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;
+ }
+ 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;
+ }
+ }
+ if (newPosition == mFrameCount) {
+ setFlags |= CBLK_BUFFER_END;
}
- if (err != NO_ERROR) {
- // probably, the client just died.
- return false;
+ 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
}
- return true;
+
+ 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..68928f1 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,6 +174,10 @@ 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;
@@ -361,15 +374,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 +392,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 +459,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 +474,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 +716,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 +743,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 +783,8 @@ 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);
+ reply->writeInt32(flags);
reply->writeInt32(sessionId);
reply->writeInt32(status);
reply->writeStrongBinder(record->asBinder());
@@ -868,13 +913,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 +1107,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..f0d75ba 100644
--- a/media/libmedia/IAudioTrack.cpp
+++ b/media/libmedia/IAudioTrack.cpp
@@ -39,6 +39,8 @@ enum {
ALLOCATE_TIMED_BUFFER,
QUEUE_TIMED_BUFFER,
SET_MEDIA_TIME_TRANSFORM,
+ SET_PARAMETERS,
+ GET_TIMESTAMP,
};
class BpAudioTrack : public BpInterface<IAudioTrack>
@@ -154,6 +156,32 @@ 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;
+ }
};
IMPLEMENT_META_INTERFACE(AudioTrack, "android.media.IAudioTrack");
@@ -223,6 +251,24 @@ 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;
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..ef99f4f 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -51,6 +51,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 +284,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) {
@@ -439,6 +456,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 +572,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 +598,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 +707,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);
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..8434d43 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,11 @@ 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) {
+ ALOGV("process %p channel %d EVENT_UNDERRUN or EVENT_BUFFER_END", this, mChannelID);
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);
}
}
@@ -884,7 +880,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..bfb730c 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,57 @@ 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) {
+ // allow failure
+ ALOGE("[%s] storeMetaDataInBuffers failed w/ err %d",
+ mComponentName.c_str(), err);
+ 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 +1652,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 +2366,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 +2502,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 +2520,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 +2652,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) {
@@ -2916,6 +3113,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 +3244,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 +3274,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 +3440,7 @@ void ACodec::UninitializedState::stateEntered() {
mCodec->mOMX.clear();
mCodec->mQuirks = 0;
mCodec->mFlags = 0;
+ mCodec->mUseMetadataOnEncoderOutput = 0;
mCodec->mComponentName.clear();
}
@@ -3373,6 +3594,7 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
if (componentName.endsWith(".secure")) {
mCodec->mFlags |= kFlagIsSecure;
+ mCodec->mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
}
mCodec->mQuirks = quirks;
@@ -3405,6 +3627,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 +3761,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 +3965,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 +4003,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 +4134,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 +4154,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 +4204,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 +4423,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..1f68b51 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 \
@@ -97,9 +98,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..e38e261 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();
@@ -274,6 +366,8 @@ void AudioPlayer::reset() {
mReachedEOS = false;
mFinalStatus = OK;
mStarted = false;
+ mPlaying = false;
+ mStartPosUs = 0;
}
// static
@@ -294,10 +388,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 +410,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;
+}
- buffer->size = numBytesWritten;
+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;
+
+ case AudioTrack::EVENT_STREAM_END:
+ mReachedEOS = true;
+ notifyAudioEOS();
+ break;
+ }
}
uint32_t AudioPlayer::getNumFramesPendingPlayout() const {
@@ -361,6 +487,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 +502,7 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) {
}
options.setSeekTo(mSeekTimeUs);
+ refreshSeekTime = true;
if (mInputBuffer != NULL) {
mInputBuffer->release();
@@ -407,43 +535,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 +595,34 @@ 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 && useOffload()) {
+ if (postSeekComplete) {
+ ALOGV("fillBuffer is going to post SEEK_COMPLETE");
+ mObserver->postAudioSeekComplete();
+ postSeekComplete = false;
+ }
+
+ mStartPosUs = mPositionTimeMediaUs;
+ ALOGV("adjust seek time to: %.2f", mStartPosUs/ 1E6);
+ }
- mPositionTimeRealUs =
- ((mNumFramesPlayed + size_done / mFrameSize) * 1000000)
- / mSampleRate;
+ 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 +648,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 +680,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,9 +711,36 @@ 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 (useOffload()) {
+ if (mSeeking) {
+ return mSeekTimeUs;
+ }
+ mPositionTimeRealUs = getOutputPlayPositionUs_l();
+ ALOGV("getMediaTimeUs getOutputPlayPositionUs_l() mPositionTimeRealUs %lld",
+ mPositionTimeRealUs);
+ return mPositionTimeRealUs;
+ }
+
if (mPositionTimeMediaUs < 0 || mPositionTimeRealUs < 0) {
if (mSeeking) {
return mSeekTimeUs;
@@ -561,8 +761,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 +776,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..5fbee7e 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;
@@ -474,6 +493,8 @@ void AwesomePlayer::reset_l() {
mDisplayWidth = 0;
mDisplayHeight = 0;
+ notifyListener_l(MEDIA_STOPPED);
+
if (mDecryptHandle != NULL) {
mDrmManagerClient->setPlaybackStatus(mDecryptHandle,
Playback::STOP, 0);
@@ -518,7 +539,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 +552,7 @@ void AwesomePlayer::reset_l() {
mAudioSource->stop();
}
mAudioSource.clear();
+ mOmxSource.clear();
mTimeSource = NULL;
@@ -586,7 +608,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 +619,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;
@@ -842,6 +864,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 +912,42 @@ 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");
+ 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 {
+ createAudioPlayer_l();
+ err = startAudioPlayer_l(false);
+ }
+ }
+ }
+
if (err != OK) {
delete mAudioPlayer;
mAudioPlayer = NULL;
@@ -966,19 +996,65 @@ status_t AwesomePlayer::play_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 +1072,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() {
@@ -1134,18 +1214,20 @@ status_t AwesomePlayer::pause_l(bool at_eos) {
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 +1372,6 @@ status_t AwesomePlayer::getPosition(int64_t *positionUs) {
} else {
*positionUs = 0;
}
-
return OK;
}
@@ -1324,6 +1405,9 @@ status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
mSeekTimeUs = timeUs;
modifyFlags((AT_EOS | AUDIO_AT_EOS | VIDEO_AT_EOS), CLEAR);
+ notifyListener_l(MEDIA_PAUSED);
+ mMediaRenderingStartGeneration = ++mStartGeneration;
+
seekAudioIfNecessary_l();
if (mFlags & TEXTPLAYER_INITIALIZED) {
@@ -1385,14 +1469,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 +1507,7 @@ status_t AwesomePlayer::initAudioDecoder() {
if (err != OK) {
mAudioSource.clear();
+ mOmxSource.clear();
return err;
}
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_QCELP)) {
@@ -1822,6 +1922,7 @@ void AwesomePlayer::onVideoEvent() {
notifyListener_l(MEDIA_INFO, MEDIA_INFO_RENDERING_START);
}
+ notifyIfMediaStarted_l();
}
mVideoBuffer->release();
@@ -1885,6 +1986,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 +2018,8 @@ void AwesomePlayer::onCheckAudioStatus() {
}
mSeeking = NO_SEEK;
+
+ notifyIfMediaStarted_l();
}
status_t finalStatus;
@@ -2200,7 +2312,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);
@@ -2273,6 +2388,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 +2523,7 @@ status_t AwesomePlayer::selectAudioTrack_l(
mAudioSource->stop();
}
mAudioSource.clear();
+ mOmxSource.clear();
mTimeSource = NULL;
@@ -2660,4 +2780,66 @@ 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
+ const bool wasPlaying = (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
+ int64_t position = 0;
+ getPosition(&position);
+
+ // 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 parepare for the host decoding
+ beginPrepareAsync_l();
+
+ if (mPrepareResult == OK) {
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ seekTo_l(position);
+ }
+
+ if (wasPlaying) {
+ modifyFlags(CACHE_UNDERRUN, CLEAR);
+ play_l();
+ }
+ }
+
+ mAudioTearDown = false;
+}
+
} // 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..9820ef5 100644
--- a/media/libstagefright/OMXClient.cpp
+++ b/media/libstagefright/OMXClient.cpp
@@ -83,6 +83,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 +117,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;
@@ -280,6 +291,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 +349,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..7b37365 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,8 +4582,15 @@ status_t QueryCodec(
OMXCodec::setComponentRole(omx, node, isEncoder, mime);
+ caps->mFlags = 0;
caps->mComponentName = componentName;
+ if (!isEncoder && !strncmp(mime, "video/", 6) &&
+ omx->storeMetaDataInBuffers(
+ node, 1 /* port index */, OMX_TRUE) == OK) {
+ caps->mFlags |= CodecCapabilities::kFlagSupportsAdaptivePlayback;
+ }
+
OMX_VIDEO_PARAM_PROFILELEVELTYPE param;
InitOMXParams(&param);
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/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..5f2b5c8 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,
@@ -99,8 +147,10 @@ SoftVPXEncoder::SoftVPXEncoder(const char *name,
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 +215,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 +297,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 +365,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 +477,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 +500,47 @@ 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;
+ }
+
+ default:
+ return SimpleSoftOMXComponent::setConfig(index, _params);
+ }
+}
+
OMX_ERRORTYPE SoftVPXEncoder::internalSetProfileLevel(
const OMX_VIDEO_PARAM_PROFILELEVELTYPE* profileAndLevel) {
if (profileAndLevel->nPortIndex != kOutputPortIndex) {
@@ -507,13 +601,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 +627,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 +650,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 +729,63 @@ 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;
+ }
+
+ 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,
@@ -676,6 +819,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..4ee5e51 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;
@@ -156,6 +163,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 +187,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..b001cf4 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,9 @@ private:
Vector<TrackStat> mTracks;
} mStats;
+ bool mOffloadAudio;
+ bool mAudioTearDown;
+
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..7e53af3 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -79,6 +79,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 +113,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..ae498b4 100644
--- a/media/libstagefright/include/OMXNodeInstance.h
+++ b/media/libstagefright/include/OMXNodeInstance.h
@@ -66,6 +66,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 +100,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/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..aaa9f89 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -345,6 +345,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 +403,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..ef683a0 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);
@@ -554,6 +570,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 +616,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 +802,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 +892,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..37d369c 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=");
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..c7d107e 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -3,11 +3,16 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- ANetworkSession.cpp \
+ MediaReceiver.cpp \
MediaSender.cpp \
Parameters.cpp \
- ParsedMessage.cpp \
+ rtp/RTPAssembler.cpp \
+ rtp/RTPReceiver.cpp \
rtp/RTPSender.cpp \
+ sink/DirectRenderer.cpp \
+ sink/WifiDisplaySink.cpp \
+ SNTPClient.cpp \
+ TimeSyncer.cpp \
source/Converter.cpp \
source/MediaPuller.cpp \
source/PlaybackSession.cpp \
@@ -57,6 +62,67 @@ LOCAL_SHARED_LIBRARIES:= \
LOCAL_MODULE:= wfd
-LOCAL_MODULE_TAGS := debug
+include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ udptest.cpp \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_wfd \
+ libutils \
+ liblog \
+
+LOCAL_MODULE:= udptest
+
+include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ rtptest.cpp \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_wfd \
+ libutils \
+ liblog \
+
+LOCAL_MODULE:= rtptest
+
+include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ nettest.cpp \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_wfd \
+ libutils \
+ liblog \
+
+LOCAL_MODULE:= nettest
include $(BUILD_EXECUTABLE)
diff --git a/media/libstagefright/wifi-display/MediaReceiver.cpp b/media/libstagefright/wifi-display/MediaReceiver.cpp
new file mode 100644
index 0000000..5524235
--- /dev/null
+++ b/media/libstagefright/wifi-display/MediaReceiver.cpp
@@ -0,0 +1,328 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaReceiver"
+#include <utils/Log.h>
+
+#include "MediaReceiver.h"
+
+#include "AnotherPacketSource.h"
+#include "rtp/RTPReceiver.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/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+MediaReceiver::MediaReceiver(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mMode(MODE_UNDEFINED),
+ mGeneration(0),
+ mInitStatus(OK),
+ mInitDoneCount(0) {
+}
+
+MediaReceiver::~MediaReceiver() {
+}
+
+ssize_t MediaReceiver::addTrack(
+ RTPReceiver::TransportMode rtpMode,
+ RTPReceiver::TransportMode rtcpMode,
+ int32_t *localRTPPort) {
+ if (mMode != MODE_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ size_t trackIndex = mTrackInfos.size();
+
+ TrackInfo info;
+
+ sp<AMessage> notify = new AMessage(kWhatReceiverNotify, id());
+ notify->setInt32("generation", mGeneration);
+ notify->setSize("trackIndex", trackIndex);
+
+ info.mReceiver = new RTPReceiver(mNetSession, notify);
+ looper()->registerHandler(info.mReceiver);
+
+ info.mReceiver->registerPacketType(
+ 33, RTPReceiver::PACKETIZATION_TRANSPORT_STREAM);
+
+ info.mReceiver->registerPacketType(
+ 96, RTPReceiver::PACKETIZATION_AAC);
+
+ info.mReceiver->registerPacketType(
+ 97, RTPReceiver::PACKETIZATION_H264);
+
+ status_t err = info.mReceiver->initAsync(
+ rtpMode,
+ rtcpMode,
+ localRTPPort);
+
+ if (err != OK) {
+ looper()->unregisterHandler(info.mReceiver->id());
+ info.mReceiver.clear();
+
+ return err;
+ }
+
+ mTrackInfos.push_back(info);
+
+ return trackIndex;
+}
+
+status_t MediaReceiver::connectTrack(
+ size_t trackIndex,
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ int32_t remoteRTCPPort) {
+ if (trackIndex >= mTrackInfos.size()) {
+ return -ERANGE;
+ }
+
+ TrackInfo *info = &mTrackInfos.editItemAt(trackIndex);
+ return info->mReceiver->connect(remoteHost, remoteRTPPort, remoteRTCPPort);
+}
+
+status_t MediaReceiver::initAsync(Mode mode) {
+ if ((mode == MODE_TRANSPORT_STREAM || mode == MODE_TRANSPORT_STREAM_RAW)
+ && mTrackInfos.size() > 1) {
+ return INVALID_OPERATION;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatInit, id());
+ msg->setInt32("mode", mode);
+ msg->post();
+
+ return OK;
+}
+
+void MediaReceiver::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatInit:
+ {
+ int32_t mode;
+ CHECK(msg->findInt32("mode", &mode));
+
+ CHECK_EQ(mMode, MODE_UNDEFINED);
+ mMode = (Mode)mode;
+
+ if (mInitStatus != OK || mInitDoneCount == mTrackInfos.size()) {
+ notifyInitDone(mInitStatus);
+ }
+
+ mTSParser = new ATSParser(
+ ATSParser::ALIGNED_VIDEO_DATA
+ | ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE);
+
+ mFormatKnownMask = 0;
+ break;
+ }
+
+ case kWhatReceiverNotify:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+ if (generation != mGeneration) {
+ break;
+ }
+
+ onReceiverNotify(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void MediaReceiver::onReceiverNotify(const sp<AMessage> &msg) {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case RTPReceiver::kWhatInitDone:
+ {
+ ++mInitDoneCount;
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ if (err != OK) {
+ mInitStatus = err;
+ ++mGeneration;
+ }
+
+ if (mMode != MODE_UNDEFINED) {
+ if (mInitStatus != OK || mInitDoneCount == mTrackInfos.size()) {
+ notifyInitDone(mInitStatus);
+ }
+ }
+ break;
+ }
+
+ case RTPReceiver::kWhatError:
+ {
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ notifyError(err);
+ break;
+ }
+
+ case RTPReceiver::kWhatAccessUnit:
+ {
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int32_t followsDiscontinuity;
+ if (!msg->findInt32(
+ "followsDiscontinuity", &followsDiscontinuity)) {
+ followsDiscontinuity = 0;
+ }
+
+ if (mMode == MODE_TRANSPORT_STREAM) {
+ if (followsDiscontinuity) {
+ mTSParser->signalDiscontinuity(
+ ATSParser::DISCONTINUITY_TIME, NULL /* extra */);
+ }
+
+ for (size_t offset = 0;
+ offset < accessUnit->size(); offset += 188) {
+ status_t err = mTSParser->feedTSPacket(
+ accessUnit->data() + offset, 188);
+
+ if (err != OK) {
+ notifyError(err);
+ break;
+ }
+ }
+
+ drainPackets(0 /* trackIndex */, ATSParser::VIDEO);
+ drainPackets(1 /* trackIndex */, ATSParser::AUDIO);
+ } else {
+ postAccessUnit(trackIndex, accessUnit, NULL);
+ }
+ break;
+ }
+
+ case RTPReceiver::kWhatPacketLost:
+ {
+ notifyPacketLost();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void MediaReceiver::drainPackets(
+ size_t trackIndex, ATSParser::SourceType type) {
+ sp<AnotherPacketSource> source =
+ static_cast<AnotherPacketSource *>(
+ mTSParser->getSource(type).get());
+
+ if (source == NULL) {
+ return;
+ }
+
+ sp<AMessage> format;
+ if (!(mFormatKnownMask & (1ul << trackIndex))) {
+ sp<MetaData> meta = source->getFormat();
+ CHECK(meta != NULL);
+
+ CHECK_EQ((status_t)OK, convertMetaDataToMessage(meta, &format));
+
+ mFormatKnownMask |= 1ul << trackIndex;
+ }
+
+ status_t finalResult;
+ while (source->hasBufferAvailable(&finalResult)) {
+ sp<ABuffer> accessUnit;
+ status_t err = source->dequeueAccessUnit(&accessUnit);
+ if (err == OK) {
+ postAccessUnit(trackIndex, accessUnit, format);
+ format.clear();
+ } else if (err != INFO_DISCONTINUITY) {
+ notifyError(err);
+ }
+ }
+
+ if (finalResult != OK) {
+ notifyError(finalResult);
+ }
+}
+
+void MediaReceiver::notifyInitDone(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInitDone);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void MediaReceiver::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void MediaReceiver::notifyPacketLost() {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatPacketLost);
+ notify->post();
+}
+
+void MediaReceiver::postAccessUnit(
+ size_t trackIndex,
+ const sp<ABuffer> &accessUnit,
+ const sp<AMessage> &format) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatAccessUnit);
+ notify->setSize("trackIndex", trackIndex);
+ notify->setBuffer("accessUnit", accessUnit);
+
+ if (format != NULL) {
+ notify->setMessage("format", format);
+ }
+
+ notify->post();
+}
+
+status_t MediaReceiver::informSender(
+ size_t trackIndex, const sp<AMessage> &params) {
+ if (trackIndex >= mTrackInfos.size()) {
+ return -ERANGE;
+ }
+
+ TrackInfo *info = &mTrackInfos.editItemAt(trackIndex);
+ return info->mReceiver->informSender(params);
+}
+
+} // namespace android
+
+
diff --git a/media/libstagefright/wifi-display/MediaReceiver.h b/media/libstagefright/wifi-display/MediaReceiver.h
new file mode 100644
index 0000000..afbb407
--- /dev/null
+++ b/media/libstagefright/wifi-display/MediaReceiver.h
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/foundation/AHandler.h>
+
+#include "ATSParser.h"
+#include "rtp/RTPReceiver.h"
+
+namespace android {
+
+struct ABuffer;
+struct ANetworkSession;
+struct AMessage;
+struct ATSParser;
+
+// This class facilitates receiving of media data for one or more tracks
+// over RTP. Either a 1:1 track to RTP channel mapping is used or a single
+// RTP channel provides the data for a transport stream that is consequently
+// demuxed and its track's data provided to the observer.
+struct MediaReceiver : public AHandler {
+ enum {
+ kWhatInitDone,
+ kWhatError,
+ kWhatAccessUnit,
+ kWhatPacketLost,
+ };
+
+ MediaReceiver(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify);
+
+ ssize_t addTrack(
+ RTPReceiver::TransportMode rtpMode,
+ RTPReceiver::TransportMode rtcpMode,
+ int32_t *localRTPPort);
+
+ status_t connectTrack(
+ size_t trackIndex,
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ int32_t remoteRTCPPort);
+
+ enum Mode {
+ MODE_UNDEFINED,
+ MODE_TRANSPORT_STREAM,
+ MODE_TRANSPORT_STREAM_RAW,
+ MODE_ELEMENTARY_STREAMS,
+ };
+ status_t initAsync(Mode mode);
+
+ status_t informSender(size_t trackIndex, const sp<AMessage> &params);
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~MediaReceiver();
+
+private:
+ enum {
+ kWhatInit,
+ kWhatReceiverNotify,
+ };
+
+ struct TrackInfo {
+ sp<RTPReceiver> mReceiver;
+ };
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+
+ Mode mMode;
+ int32_t mGeneration;
+
+ Vector<TrackInfo> mTrackInfos;
+
+ status_t mInitStatus;
+ size_t mInitDoneCount;
+
+ sp<ATSParser> mTSParser;
+ uint32_t mFormatKnownMask;
+
+ void onReceiverNotify(const sp<AMessage> &msg);
+
+ void drainPackets(size_t trackIndex, ATSParser::SourceType type);
+
+ void notifyInitDone(status_t err);
+ void notifyError(status_t err);
+ void notifyPacketLost();
+
+ void postAccessUnit(
+ size_t trackIndex,
+ const sp<ABuffer> &accessUnit,
+ const sp<AMessage> &format);
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaReceiver);
+};
+
+} // namespace android
+
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/SNTPClient.cpp b/media/libstagefright/wifi-display/SNTPClient.cpp
new file mode 100644
index 0000000..5c0af6a
--- /dev/null
+++ b/media/libstagefright/wifi-display/SNTPClient.cpp
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+#include "SNTPClient.h"
+
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/Utils.h>
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+namespace android {
+
+SNTPClient::SNTPClient() {
+}
+
+status_t SNTPClient::requestTime(const char *host) {
+ struct hostent *ent;
+ int64_t requestTimeNTP, requestTimeUs;
+ ssize_t n;
+ int64_t responseTimeUs, responseTimeNTP;
+ int64_t originateTimeNTP, receiveTimeNTP, transmitTimeNTP;
+ int64_t roundTripTimeNTP, clockOffsetNTP;
+
+ status_t err = UNKNOWN_ERROR;
+
+ int s = socket(AF_INET, SOCK_DGRAM, 0);
+
+ if (s < 0) {
+ err = -errno;
+
+ goto bail;
+ }
+
+ ent = gethostbyname(host);
+
+ if (ent == NULL) {
+ err = -ENOENT;
+ goto bail2;
+ }
+
+ struct sockaddr_in hostAddr;
+ memset(hostAddr.sin_zero, 0, sizeof(hostAddr.sin_zero));
+ hostAddr.sin_family = AF_INET;
+ hostAddr.sin_port = htons(kNTPPort);
+ hostAddr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;
+
+ uint8_t packet[kNTPPacketSize];
+ memset(packet, 0, sizeof(packet));
+
+ packet[0] = kNTPModeClient | (kNTPVersion << 3);
+
+ requestTimeNTP = getNowNTP();
+ requestTimeUs = ALooper::GetNowUs();
+ writeTimeStamp(&packet[kNTPTransmitTimeOffset], requestTimeNTP);
+
+ n = sendto(
+ s, packet, sizeof(packet), 0,
+ (const struct sockaddr *)&hostAddr, sizeof(hostAddr));
+
+ if (n < 0) {
+ err = -errno;
+ goto bail2;
+ }
+
+ memset(packet, 0, sizeof(packet));
+
+ do {
+ n = recv(s, packet, sizeof(packet), 0);
+ } while (n < 0 && errno == EINTR);
+
+ if (n < 0) {
+ err = -errno;
+ goto bail2;
+ }
+
+ responseTimeUs = ALooper::GetNowUs();
+
+ responseTimeNTP = requestTimeNTP + makeNTP(responseTimeUs - requestTimeUs);
+
+ originateTimeNTP = readTimeStamp(&packet[kNTPOriginateTimeOffset]);
+ receiveTimeNTP = readTimeStamp(&packet[kNTPReceiveTimeOffset]);
+ transmitTimeNTP = readTimeStamp(&packet[kNTPTransmitTimeOffset]);
+
+ roundTripTimeNTP =
+ makeNTP(responseTimeUs - requestTimeUs)
+ - (transmitTimeNTP - receiveTimeNTP);
+
+ clockOffsetNTP =
+ ((receiveTimeNTP - originateTimeNTP)
+ + (transmitTimeNTP - responseTimeNTP)) / 2;
+
+ mTimeReferenceNTP = responseTimeNTP + clockOffsetNTP;
+ mTimeReferenceUs = responseTimeUs;
+ mRoundTripTimeNTP = roundTripTimeNTP;
+
+ err = OK;
+
+bail2:
+ close(s);
+ s = -1;
+
+bail:
+ return err;
+}
+
+int64_t SNTPClient::adjustTimeUs(int64_t timeUs) const {
+ uint64_t nowNTP =
+ mTimeReferenceNTP + makeNTP(timeUs - mTimeReferenceUs);
+
+ int64_t nowUs =
+ (nowNTP >> 32) * 1000000ll
+ + ((nowNTP & 0xffffffff) * 1000000ll) / (1ll << 32);
+
+ nowUs -= ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+ return nowUs;
+}
+
+// static
+void SNTPClient::writeTimeStamp(uint8_t *dst, uint64_t ntpTime) {
+ *dst++ = (ntpTime >> 56) & 0xff;
+ *dst++ = (ntpTime >> 48) & 0xff;
+ *dst++ = (ntpTime >> 40) & 0xff;
+ *dst++ = (ntpTime >> 32) & 0xff;
+ *dst++ = (ntpTime >> 24) & 0xff;
+ *dst++ = (ntpTime >> 16) & 0xff;
+ *dst++ = (ntpTime >> 8) & 0xff;
+ *dst++ = ntpTime & 0xff;
+}
+
+// static
+uint64_t SNTPClient::readTimeStamp(const uint8_t *dst) {
+ return U64_AT(dst);
+}
+
+// static
+uint64_t SNTPClient::getNowNTP() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL /* time zone */);
+
+ uint64_t nowUs = tv.tv_sec * 1000000ll + tv.tv_usec;
+
+ nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+ return makeNTP(nowUs);
+}
+
+// static
+uint64_t SNTPClient::makeNTP(uint64_t deltaUs) {
+ uint64_t hi = deltaUs / 1000000ll;
+ uint64_t lo = ((1ll << 32) * (deltaUs % 1000000ll)) / 1000000ll;
+
+ return (hi << 32) | lo;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/SNTPClient.h b/media/libstagefright/wifi-display/SNTPClient.h
new file mode 100644
index 0000000..967d1fc
--- /dev/null
+++ b/media/libstagefright/wifi-display/SNTPClient.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#ifndef SNTP_CLIENT_H_
+
+#define SNTP_CLIENT_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+// Implementation of the SNTP (Simple Network Time Protocol)
+struct SNTPClient {
+ SNTPClient();
+
+ status_t requestTime(const char *host);
+
+ // given a time obtained from ALooper::GetNowUs()
+ // return the number of us elapsed since Jan 1 1970 00:00:00 (UTC).
+ int64_t adjustTimeUs(int64_t timeUs) const;
+
+private:
+ enum {
+ kNTPPort = 123,
+ kNTPPacketSize = 48,
+ kNTPModeClient = 3,
+ kNTPVersion = 3,
+ kNTPTransmitTimeOffset = 40,
+ kNTPOriginateTimeOffset = 24,
+ kNTPReceiveTimeOffset = 32,
+ };
+
+ uint64_t mTimeReferenceNTP;
+ int64_t mTimeReferenceUs;
+ int64_t mRoundTripTimeNTP;
+
+ static void writeTimeStamp(uint8_t *dst, uint64_t ntpTime);
+ static uint64_t readTimeStamp(const uint8_t *dst);
+
+ static uint64_t getNowNTP();
+ static uint64_t makeNTP(uint64_t deltaUs);
+
+ DISALLOW_EVIL_CONSTRUCTORS(SNTPClient);
+};
+
+} // namespace android
+
+#endif // SNTP_CLIENT_H_
diff --git a/media/libstagefright/wifi-display/TimeSyncer.cpp b/media/libstagefright/wifi-display/TimeSyncer.cpp
new file mode 100644
index 0000000..0f4d93a
--- /dev/null
+++ b/media/libstagefright/wifi-display/TimeSyncer.cpp
@@ -0,0 +1,337 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NEBUG 0
+#define LOG_TAG "TimeSyncer"
+#include <utils/Log.h>
+
+#include "TimeSyncer.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+TimeSyncer::TimeSyncer(
+ const sp<ANetworkSession> &netSession, const sp<AMessage> &notify)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mIsServer(false),
+ mConnected(false),
+ mUDPSession(0),
+ mSeqNo(0),
+ mTotalTimeUs(0.0),
+ mPendingT1(0ll),
+ mTimeoutGeneration(0) {
+}
+
+TimeSyncer::~TimeSyncer() {
+}
+
+void TimeSyncer::startServer(unsigned localPort) {
+ sp<AMessage> msg = new AMessage(kWhatStartServer, id());
+ msg->setInt32("localPort", localPort);
+ msg->post();
+}
+
+void TimeSyncer::startClient(const char *remoteHost, unsigned remotePort) {
+ sp<AMessage> msg = new AMessage(kWhatStartClient, id());
+ msg->setString("remoteHost", remoteHost);
+ msg->setInt32("remotePort", remotePort);
+ msg->post();
+}
+
+void TimeSyncer::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStartClient:
+ {
+ AString remoteHost;
+ CHECK(msg->findString("remoteHost", &remoteHost));
+
+ int32_t remotePort;
+ CHECK(msg->findInt32("remotePort", &remotePort));
+
+ sp<AMessage> notify = new AMessage(kWhatUDPNotify, id());
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createUDPSession(
+ 0 /* localPort */,
+ remoteHost.c_str(),
+ remotePort,
+ notify,
+ &mUDPSession));
+
+ postSendPacket();
+ break;
+ }
+
+ case kWhatStartServer:
+ {
+ mIsServer = true;
+
+ int32_t localPort;
+ CHECK(msg->findInt32("localPort", &localPort));
+
+ sp<AMessage> notify = new AMessage(kWhatUDPNotify, id());
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createUDPSession(
+ localPort, notify, &mUDPSession));
+
+ break;
+ }
+
+ case kWhatSendPacket:
+ {
+ if (mHistory.size() == 0) {
+ ALOGI("starting batch");
+ }
+
+ TimeInfo ti;
+ memset(&ti, 0, sizeof(ti));
+
+ ti.mT1 = ALooper::GetNowUs();
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->sendRequest(
+ mUDPSession, &ti, sizeof(ti)));
+
+ mPendingT1 = ti.mT1;
+ postTimeout();
+ break;
+ }
+
+ case kWhatTimedOut:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mTimeoutGeneration) {
+ break;
+ }
+
+ ALOGI("timed out, sending another request");
+ postSendPacket();
+ break;
+ }
+
+ case kWhatUDPNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred in session %d (%d, '%s/%s').",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ mNetSession->destroySession(sessionID);
+
+ cancelTimeout();
+
+ notifyError(err);
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ sp<ABuffer> packet;
+ CHECK(msg->findBuffer("data", &packet));
+
+ int64_t arrivalTimeUs;
+ CHECK(packet->meta()->findInt64(
+ "arrivalTimeUs", &arrivalTimeUs));
+
+ CHECK_EQ(packet->size(), sizeof(TimeInfo));
+
+ TimeInfo *ti = (TimeInfo *)packet->data();
+
+ if (mIsServer) {
+ if (!mConnected) {
+ AString fromAddr;
+ CHECK(msg->findString("fromAddr", &fromAddr));
+
+ int32_t fromPort;
+ CHECK(msg->findInt32("fromPort", &fromPort));
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->connectUDPSession(
+ mUDPSession, fromAddr.c_str(), fromPort));
+
+ mConnected = true;
+ }
+
+ ti->mT2 = arrivalTimeUs;
+ ti->mT3 = ALooper::GetNowUs();
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->sendRequest(
+ mUDPSession, ti, sizeof(*ti)));
+ } else {
+ if (ti->mT1 != mPendingT1) {
+ break;
+ }
+
+ cancelTimeout();
+ mPendingT1 = 0;
+
+ ti->mT4 = arrivalTimeUs;
+
+ // One way delay for a packet to travel from client
+ // to server or back (assumed to be the same either way).
+ int64_t delay =
+ (ti->mT2 - ti->mT1 + ti->mT4 - ti->mT3) / 2;
+
+ // Offset between the client clock (T1, T4) and the
+ // server clock (T2, T3) timestamps.
+ int64_t offset =
+ (ti->mT2 - ti->mT1 - ti->mT4 + ti->mT3) / 2;
+
+ mHistory.push_back(*ti);
+
+ ALOGV("delay = %lld us,\toffset %lld us",
+ delay,
+ offset);
+
+ if (mHistory.size() < kNumPacketsPerBatch) {
+ postSendPacket(1000000ll / 30);
+ } else {
+ notifyOffset();
+
+ ALOGI("batch done");
+
+ mHistory.clear();
+ postSendPacket(kBatchDelayUs);
+ }
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void TimeSyncer::postSendPacket(int64_t delayUs) {
+ (new AMessage(kWhatSendPacket, id()))->post(delayUs);
+}
+
+void TimeSyncer::postTimeout() {
+ sp<AMessage> msg = new AMessage(kWhatTimedOut, id());
+ msg->setInt32("generation", mTimeoutGeneration);
+ msg->post(kTimeoutDelayUs);
+}
+
+void TimeSyncer::cancelTimeout() {
+ ++mTimeoutGeneration;
+}
+
+void TimeSyncer::notifyError(status_t err) {
+ if (mNotify == NULL) {
+ looper()->stop();
+ return;
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+// static
+int TimeSyncer::CompareRountripTime(const TimeInfo *ti1, const TimeInfo *ti2) {
+ int64_t rt1 = ti1->mT4 - ti1->mT1;
+ int64_t rt2 = ti2->mT4 - ti2->mT1;
+
+ if (rt1 < rt2) {
+ return -1;
+ } else if (rt1 > rt2) {
+ return 1;
+ }
+
+ return 0;
+}
+
+void TimeSyncer::notifyOffset() {
+ mHistory.sort(CompareRountripTime);
+
+ int64_t sum = 0ll;
+ size_t count = 0;
+
+ // Only consider the third of the information associated with the best
+ // (smallest) roundtrip times.
+ for (size_t i = 0; i < mHistory.size() / 3; ++i) {
+ const TimeInfo *ti = &mHistory[i];
+
+#if 0
+ // One way delay for a packet to travel from client
+ // to server or back (assumed to be the same either way).
+ int64_t delay =
+ (ti->mT2 - ti->mT1 + ti->mT4 - ti->mT3) / 2;
+#endif
+
+ // Offset between the client clock (T1, T4) and the
+ // server clock (T2, T3) timestamps.
+ int64_t offset =
+ (ti->mT2 - ti->mT1 - ti->mT4 + ti->mT3) / 2;
+
+ ALOGV("(%d) RT: %lld us, offset: %lld us",
+ i, ti->mT4 - ti->mT1, offset);
+
+ sum += offset;
+ ++count;
+ }
+
+ if (mNotify == NULL) {
+ ALOGI("avg. offset is %lld", sum / count);
+ return;
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatTimeOffset);
+ notify->setInt64("offset", sum / count);
+ notify->post();
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/TimeSyncer.h b/media/libstagefright/wifi-display/TimeSyncer.h
new file mode 100644
index 0000000..4e7571f
--- /dev/null
+++ b/media/libstagefright/wifi-display/TimeSyncer.h
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#ifndef TIME_SYNCER_H_
+
+#define TIME_SYNCER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ANetworkSession;
+
+/*
+ TimeSyncer allows us to synchronize time between a client and a server.
+ The client sends a UDP packet containing its send-time to the server,
+ the server sends that packet back to the client amended with information
+ about when it was received as well as the time the reply was sent back.
+ Finally the client receives the reply and has now enough information to
+ compute the clock offset between client and server assuming that packet
+ exchange is symmetric, i.e. time for a packet client->server and
+ server->client is roughly equal.
+ This exchange is repeated a number of times and the average offset computed
+ over the 30% of packets that had the lowest roundtrip times.
+ The offset is determined every 10 secs to account for slight differences in
+ clock frequency.
+*/
+struct TimeSyncer : public AHandler {
+ enum {
+ kWhatError,
+ kWhatTimeOffset,
+ };
+ TimeSyncer(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify);
+
+ void startServer(unsigned localPort);
+ void startClient(const char *remoteHost, unsigned remotePort);
+
+protected:
+ virtual ~TimeSyncer();
+
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatStartServer,
+ kWhatStartClient,
+ kWhatUDPNotify,
+ kWhatSendPacket,
+ kWhatTimedOut,
+ };
+
+ struct TimeInfo {
+ int64_t mT1; // client timestamp at send
+ int64_t mT2; // server timestamp at receive
+ int64_t mT3; // server timestamp at send
+ int64_t mT4; // client timestamp at receive
+ };
+
+ enum {
+ kNumPacketsPerBatch = 30,
+ };
+ static const int64_t kTimeoutDelayUs = 500000ll;
+ static const int64_t kBatchDelayUs = 60000000ll; // every minute
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+
+ bool mIsServer;
+ bool mConnected;
+ int32_t mUDPSession;
+ uint32_t mSeqNo;
+ double mTotalTimeUs;
+
+ Vector<TimeInfo> mHistory;
+
+ int64_t mPendingT1;
+ int32_t mTimeoutGeneration;
+
+ void postSendPacket(int64_t delayUs = 0ll);
+
+ void postTimeout();
+ void cancelTimeout();
+
+ void notifyError(status_t err);
+ void notifyOffset();
+
+ static int CompareRountripTime(const TimeInfo *ti1, const TimeInfo *ti2);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TimeSyncer);
+};
+
+} // namespace android
+
+#endif // TIME_SYNCER_H_
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/nettest.cpp b/media/libstagefright/wifi-display/nettest.cpp
new file mode 100644
index 0000000..73c0d80
--- /dev/null
+++ b/media/libstagefright/wifi-display/nettest.cpp
@@ -0,0 +1,400 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NEBUG 0
+#define LOG_TAG "nettest"
+#include <utils/Log.h>
+
+#include "TimeSyncer.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+struct TestHandler : public AHandler {
+ TestHandler(const sp<ANetworkSession> &netSession);
+
+ void listen(int32_t port);
+ void connect(const char *host, int32_t port);
+
+protected:
+ virtual ~TestHandler();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kTimeSyncerPort = 8123,
+ };
+
+ enum {
+ kWhatListen,
+ kWhatConnect,
+ kWhatTimeSyncerNotify,
+ kWhatNetNotify,
+ kWhatSendMore,
+ kWhatStop,
+ };
+
+ sp<ANetworkSession> mNetSession;
+ sp<TimeSyncer> mTimeSyncer;
+
+ int32_t mServerSessionID;
+ int32_t mSessionID;
+
+ int64_t mTimeOffsetUs;
+ bool mTimeOffsetValid;
+
+ int32_t mCounter;
+
+ int64_t mMaxDelayMs;
+
+ void dumpDelay(int32_t counter, int64_t delayMs);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TestHandler);
+};
+
+TestHandler::TestHandler(const sp<ANetworkSession> &netSession)
+ : mNetSession(netSession),
+ mServerSessionID(0),
+ mSessionID(0),
+ mTimeOffsetUs(-1ll),
+ mTimeOffsetValid(false),
+ mCounter(0),
+ mMaxDelayMs(-1ll) {
+}
+
+TestHandler::~TestHandler() {
+}
+
+void TestHandler::listen(int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatListen, id());
+ msg->setInt32("port", port);
+ msg->post();
+}
+
+void TestHandler::connect(const char *host, int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatConnect, id());
+ msg->setString("host", host);
+ msg->setInt32("port", port);
+ msg->post();
+}
+
+void TestHandler::dumpDelay(int32_t counter, int64_t delayMs) {
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ if (delayMs > mMaxDelayMs) {
+ mMaxDelayMs = delayMs;
+ }
+
+ ALOGI("[%d] (%4lld ms / %4lld ms) %s",
+ counter,
+ delayMs,
+ mMaxDelayMs,
+ kPattern + kPatternSize - n);
+}
+
+void TestHandler::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatListen:
+ {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ notify = new AMessage(kWhatNetNotify, id());
+
+ int32_t port;
+ CHECK(msg->findInt32("port", &port));
+
+ struct in_addr ifaceAddr;
+ ifaceAddr.s_addr = INADDR_ANY;
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createTCPDatagramSession(
+ ifaceAddr,
+ port,
+ notify,
+ &mServerSessionID));
+ break;
+ }
+
+ case kWhatConnect:
+ {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+ mTimeSyncer->startServer(kTimeSyncerPort);
+
+ AString host;
+ CHECK(msg->findString("host", &host));
+
+ int32_t port;
+ CHECK(msg->findInt32("port", &port));
+
+ notify = new AMessage(kWhatNetNotify, id());
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createTCPDatagramSession(
+ 0 /* localPort */,
+ host.c_str(),
+ port,
+ notify,
+ &mSessionID));
+ break;
+ }
+
+ case kWhatNetNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatConnected:
+ {
+ ALOGI("kWhatConnected");
+
+ (new AMessage(kWhatSendMore, id()))->post();
+ break;
+ }
+
+ case ANetworkSession::kWhatClientConnected:
+ {
+ ALOGI("kWhatClientConnected");
+
+ CHECK_EQ(mSessionID, 0);
+ CHECK(msg->findInt32("sessionID", &mSessionID));
+
+ AString clientIP;
+ CHECK(msg->findString("client-ip", &clientIP));
+
+ mTimeSyncer->startClient(clientIP.c_str(), kTimeSyncerPort);
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ sp<ABuffer> packet;
+ CHECK(msg->findBuffer("data", &packet));
+
+ CHECK_EQ(packet->size(), 12u);
+
+ int32_t counter = U32_AT(packet->data());
+ int64_t timeUs = U64_AT(packet->data() + 4);
+
+ if (mTimeOffsetValid) {
+ timeUs -= mTimeOffsetUs;
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayMs = (nowUs - timeUs) / 1000ll;
+
+ dumpDelay(counter, delayMs);
+ } else {
+ ALOGI("received %d", counter);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatError:
+ {
+ ALOGE("kWhatError");
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatTimeSyncerNotify:
+ {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+ break;
+ }
+
+ case kWhatSendMore:
+ {
+ uint8_t buffer[4 + 8];
+ buffer[0] = mCounter >> 24;
+ buffer[1] = (mCounter >> 16) & 0xff;
+ buffer[2] = (mCounter >> 8) & 0xff;
+ buffer[3] = mCounter & 0xff;
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ buffer[4] = nowUs >> 56;
+ buffer[5] = (nowUs >> 48) & 0xff;
+ buffer[6] = (nowUs >> 40) & 0xff;
+ buffer[7] = (nowUs >> 32) & 0xff;
+ buffer[8] = (nowUs >> 24) & 0xff;
+ buffer[9] = (nowUs >> 16) & 0xff;
+ buffer[10] = (nowUs >> 8) & 0xff;
+ buffer[11] = nowUs & 0xff;
+
+ ++mCounter;
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->sendRequest(
+ mSessionID,
+ buffer,
+ sizeof(buffer),
+ true /* timeValid */,
+ nowUs));
+
+ msg->post(100000ll);
+ break;
+ }
+
+ case kWhatStop:
+ {
+ if (mSessionID != 0) {
+ mNetSession->destroySession(mSessionID);
+ mSessionID = 0;
+ }
+
+ if (mServerSessionID != 0) {
+ mNetSession->destroySession(mServerSessionID);
+ mServerSessionID = 0;
+ }
+
+ looper()->stop();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+} // namespace android
+
+static void usage(const char *me) {
+ fprintf(stderr,
+ "usage: %s -c host:port\tconnect to remote host\n"
+ " -l port \tlisten\n",
+ me);
+}
+
+int main(int argc, char **argv) {
+ using namespace android;
+
+ // srand(time(NULL));
+
+ ProcessState::self()->startThreadPool();
+
+ DataSource::RegisterDefaultSniffers();
+
+ int32_t connectToPort = -1;
+ AString connectToHost;
+
+ int32_t listenOnPort = -1;
+
+ int res;
+ while ((res = getopt(argc, argv, "hc:l:")) >= 0) {
+ switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 0 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case 'l':
+ {
+ char *end;
+ listenOnPort = strtol(optarg, &end, 10);
+
+ if (*end != '\0' || end == optarg
+ || listenOnPort < 0 || listenOnPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case '?':
+ case 'h':
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if ((listenOnPort < 0 && connectToPort < 0)
+ || (listenOnPort >= 0 && connectToPort >= 0)) {
+ fprintf(stderr,
+ "You need to select either client or server mode.\n");
+ exit(1);
+ }
+
+ sp<ANetworkSession> netSession = new ANetworkSession;
+ netSession->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<TestHandler> handler = new TestHandler(netSession);
+ looper->registerHandler(handler);
+
+ if (listenOnPort) {
+ handler->listen(listenOnPort);
+ }
+
+ if (connectToPort >= 0) {
+ handler->connect(connectToHost.c_str(), connectToPort);
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ return 0;
+}
diff --git a/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
new file mode 100644
index 0000000..7a96081
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
@@ -0,0 +1,328 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "RTPAssembler"
+#include <utils/Log.h>
+
+#include "RTPAssembler.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+RTPReceiver::Assembler::Assembler(const sp<AMessage> &notify)
+ : mNotify(notify) {
+}
+
+void RTPReceiver::Assembler::postAccessUnit(
+ const sp<ABuffer> &accessUnit, bool followsDiscontinuity) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", RTPReceiver::kWhatAccessUnit);
+ notify->setBuffer("accessUnit", accessUnit);
+ notify->setInt32("followsDiscontinuity", followsDiscontinuity);
+ notify->post();
+}
+////////////////////////////////////////////////////////////////////////////////
+
+RTPReceiver::TSAssembler::TSAssembler(const sp<AMessage> &notify)
+ : Assembler(notify),
+ mSawDiscontinuity(false) {
+}
+
+void RTPReceiver::TSAssembler::signalDiscontinuity() {
+ mSawDiscontinuity = true;
+}
+
+status_t RTPReceiver::TSAssembler::processPacket(const sp<ABuffer> &packet) {
+ int32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", &rtpTime));
+
+ packet->meta()->setInt64("timeUs", (rtpTime * 100ll) / 9);
+
+ postAccessUnit(packet, mSawDiscontinuity);
+
+ if (mSawDiscontinuity) {
+ mSawDiscontinuity = false;
+ }
+
+ return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+RTPReceiver::H264Assembler::H264Assembler(const sp<AMessage> &notify)
+ : Assembler(notify),
+ mState(0),
+ mIndicator(0),
+ mNALType(0),
+ mAccessUnitRTPTime(0) {
+}
+
+void RTPReceiver::H264Assembler::signalDiscontinuity() {
+ reset();
+}
+
+status_t RTPReceiver::H264Assembler::processPacket(const sp<ABuffer> &packet) {
+ status_t err = internalProcessPacket(packet);
+
+ if (err != OK) {
+ reset();
+ }
+
+ return err;
+}
+
+status_t RTPReceiver::H264Assembler::internalProcessPacket(
+ const sp<ABuffer> &packet) {
+ const uint8_t *data = packet->data();
+ size_t size = packet->size();
+
+ switch (mState) {
+ case 0:
+ {
+ if (size < 1 || (data[0] & 0x80)) {
+ ALOGV("Malformed H264 RTP packet (empty or F-bit set)");
+ return ERROR_MALFORMED;
+ }
+
+ unsigned nalType = data[0] & 0x1f;
+ if (nalType >= 1 && nalType <= 23) {
+ addSingleNALUnit(packet);
+ ALOGV("added single NAL packet");
+ } else if (nalType == 28) {
+ // FU-A
+ unsigned indicator = data[0];
+ CHECK((indicator & 0x1f) == 28);
+
+ if (size < 2) {
+ ALOGV("Malformed H264 FU-A packet (single byte)");
+ return ERROR_MALFORMED;
+ }
+
+ if (!(data[1] & 0x80)) {
+ ALOGV("Malformed H264 FU-A packet (no start bit)");
+ return ERROR_MALFORMED;
+ }
+
+ mIndicator = data[0];
+ mNALType = data[1] & 0x1f;
+ uint32_t nri = (data[0] >> 5) & 3;
+
+ clearAccumulator();
+
+ uint8_t byte = mNALType | (nri << 5);
+ appendToAccumulator(&byte, 1);
+ appendToAccumulator(data + 2, size - 2);
+
+ int32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", &rtpTime));
+ mAccumulator->meta()->setInt32("rtp-time", rtpTime);
+
+ if (data[1] & 0x40) {
+ // Huh? End bit also set on the first buffer.
+ addSingleNALUnit(mAccumulator);
+ clearAccumulator();
+
+ ALOGV("added FU-A");
+ break;
+ }
+
+ mState = 1;
+ } else if (nalType == 24) {
+ // STAP-A
+
+ status_t err = addSingleTimeAggregationPacket(packet);
+ if (err != OK) {
+ return err;
+ }
+ } else {
+ ALOGV("Malformed H264 packet (unknown type %d)", nalType);
+ return ERROR_UNSUPPORTED;
+ }
+ break;
+ }
+
+ case 1:
+ {
+ if (size < 2
+ || data[0] != mIndicator
+ || (data[1] & 0x1f) != mNALType
+ || (data[1] & 0x80)) {
+ ALOGV("Malformed H264 FU-A packet (indicator, "
+ "type or start bit mismatch)");
+
+ return ERROR_MALFORMED;
+ }
+
+ appendToAccumulator(data + 2, size - 2);
+
+ if (data[1] & 0x40) {
+ addSingleNALUnit(mAccumulator);
+
+ clearAccumulator();
+ mState = 0;
+
+ ALOGV("added FU-A");
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+
+ int32_t marker;
+ CHECK(packet->meta()->findInt32("M", &marker));
+
+ if (marker) {
+ flushAccessUnit();
+ }
+
+ return OK;
+}
+
+void RTPReceiver::H264Assembler::reset() {
+ mNALUnits.clear();
+
+ clearAccumulator();
+ mState = 0;
+}
+
+void RTPReceiver::H264Assembler::clearAccumulator() {
+ if (mAccumulator != NULL) {
+ // XXX Too expensive.
+ mAccumulator.clear();
+ }
+}
+
+void RTPReceiver::H264Assembler::appendToAccumulator(
+ const void *data, size_t size) {
+ if (mAccumulator == NULL) {
+ mAccumulator = new ABuffer(size);
+ memcpy(mAccumulator->data(), data, size);
+ return;
+ }
+
+ if (mAccumulator->size() + size > mAccumulator->capacity()) {
+ sp<ABuffer> buf = new ABuffer(mAccumulator->size() + size);
+ memcpy(buf->data(), mAccumulator->data(), mAccumulator->size());
+ buf->setRange(0, mAccumulator->size());
+
+ int32_t rtpTime;
+ if (mAccumulator->meta()->findInt32("rtp-time", &rtpTime)) {
+ buf->meta()->setInt32("rtp-time", rtpTime);
+ }
+
+ mAccumulator = buf;
+ }
+
+ memcpy(mAccumulator->data() + mAccumulator->size(), data, size);
+ mAccumulator->setRange(0, mAccumulator->size() + size);
+}
+
+void RTPReceiver::H264Assembler::addSingleNALUnit(const sp<ABuffer> &packet) {
+ if (mNALUnits.empty()) {
+ int32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", &rtpTime));
+
+ mAccessUnitRTPTime = rtpTime;
+ }
+
+ mNALUnits.push_back(packet);
+}
+
+void RTPReceiver::H264Assembler::flushAccessUnit() {
+ if (mNALUnits.empty()) {
+ return;
+ }
+
+ size_t totalSize = 0;
+ for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
+ it != mNALUnits.end(); ++it) {
+ totalSize += 4 + (*it)->size();
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ size_t offset = 0;
+ for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
+ it != mNALUnits.end(); ++it) {
+ const sp<ABuffer> nalUnit = *it;
+
+ memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4);
+
+ memcpy(accessUnit->data() + offset + 4,
+ nalUnit->data(),
+ nalUnit->size());
+
+ offset += 4 + nalUnit->size();
+ }
+
+ mNALUnits.clear();
+
+ accessUnit->meta()->setInt64("timeUs", mAccessUnitRTPTime * 100ll / 9ll);
+ postAccessUnit(accessUnit, false /* followsDiscontinuity */);
+}
+
+status_t RTPReceiver::H264Assembler::addSingleTimeAggregationPacket(
+ const sp<ABuffer> &packet) {
+ const uint8_t *data = packet->data();
+ size_t size = packet->size();
+
+ if (size < 3) {
+ ALOGV("Malformed H264 STAP-A packet (too small)");
+ return ERROR_MALFORMED;
+ }
+
+ int32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", &rtpTime));
+
+ ++data;
+ --size;
+ while (size >= 2) {
+ size_t nalSize = (data[0] << 8) | data[1];
+
+ if (size < nalSize + 2) {
+ ALOGV("Malformed H264 STAP-A packet (incomplete NAL unit)");
+ return ERROR_MALFORMED;
+ }
+
+ sp<ABuffer> unit = new ABuffer(nalSize);
+ memcpy(unit->data(), &data[2], nalSize);
+
+ unit->meta()->setInt32("rtp-time", rtpTime);
+
+ addSingleNALUnit(unit);
+
+ data += 2 + nalSize;
+ size -= 2 + nalSize;
+ }
+
+ if (size != 0) {
+ ALOGV("Unexpected padding at end of STAP-A packet.");
+ }
+
+ ALOGV("added STAP-A");
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPAssembler.h b/media/libstagefright/wifi-display/rtp/RTPAssembler.h
new file mode 100644
index 0000000..e456d32
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPAssembler.h
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#ifndef RTP_ASSEMBLER_H_
+
+#define RTP_ASSEMBLER_H_
+
+#include "RTPReceiver.h"
+
+namespace android {
+
+// A helper class to reassemble the payload of RTP packets into access
+// units depending on the packetization scheme.
+struct RTPReceiver::Assembler : public RefBase {
+ Assembler(const sp<AMessage> &notify);
+
+ virtual void signalDiscontinuity() = 0;
+ virtual status_t processPacket(const sp<ABuffer> &packet) = 0;
+
+protected:
+ virtual ~Assembler() {}
+
+ void postAccessUnit(
+ const sp<ABuffer> &accessUnit, bool followsDiscontinuity);
+
+private:
+ sp<AMessage> mNotify;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Assembler);
+};
+
+struct RTPReceiver::TSAssembler : public RTPReceiver::Assembler {
+ TSAssembler(const sp<AMessage> &notify);
+
+ virtual void signalDiscontinuity();
+ virtual status_t processPacket(const sp<ABuffer> &packet);
+
+private:
+ bool mSawDiscontinuity;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TSAssembler);
+};
+
+struct RTPReceiver::H264Assembler : public RTPReceiver::Assembler {
+ H264Assembler(const sp<AMessage> &notify);
+
+ virtual void signalDiscontinuity();
+ virtual status_t processPacket(const sp<ABuffer> &packet);
+
+private:
+ int32_t mState;
+
+ uint8_t mIndicator;
+ uint8_t mNALType;
+
+ sp<ABuffer> mAccumulator;
+
+ List<sp<ABuffer> > mNALUnits;
+ int32_t mAccessUnitRTPTime;
+
+ status_t internalProcessPacket(const sp<ABuffer> &packet);
+
+ void addSingleNALUnit(const sp<ABuffer> &packet);
+ status_t addSingleTimeAggregationPacket(const sp<ABuffer> &packet);
+
+ void flushAccessUnit();
+
+ void clearAccumulator();
+ void appendToAccumulator(const void *data, size_t size);
+
+ void reset();
+
+ DISALLOW_EVIL_CONSTRUCTORS(H264Assembler);
+};
+
+} // namespace android
+
+#endif // RTP_ASSEMBLER_H_
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
new file mode 100644
index 0000000..3b3bd63
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
@@ -0,0 +1,1152 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "RTPReceiver"
+#include <utils/Log.h>
+
+#include "RTPAssembler.h"
+#include "RTPReceiver.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>
+
+#define TRACK_PACKET_LOSS 0
+
+namespace android {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct RTPReceiver::Source : public AHandler {
+ Source(RTPReceiver *receiver, uint32_t ssrc);
+
+ void onPacketReceived(uint16_t seq, const sp<ABuffer> &buffer);
+
+ void addReportBlock(uint32_t ssrc, const sp<ABuffer> &buf);
+
+protected:
+ virtual ~Source();
+
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatRetransmit,
+ kWhatDeclareLost,
+ };
+
+ static const uint32_t kMinSequential = 2;
+ static const uint32_t kMaxDropout = 3000;
+ static const uint32_t kMaxMisorder = 100;
+ static const uint32_t kRTPSeqMod = 1u << 16;
+ static const int64_t kReportIntervalUs = 10000000ll;
+
+ RTPReceiver *mReceiver;
+ uint32_t mSSRC;
+ bool mFirst;
+ uint16_t mMaxSeq;
+ uint32_t mCycles;
+ uint32_t mBaseSeq;
+ uint32_t mReceived;
+ uint32_t mExpectedPrior;
+ uint32_t mReceivedPrior;
+
+ int64_t mFirstArrivalTimeUs;
+ int64_t mFirstRTPTimeUs;
+
+ // Ordered by extended seq number.
+ List<sp<ABuffer> > mPackets;
+
+ enum StatusBits {
+ STATUS_DECLARED_LOST = 1,
+ STATUS_REQUESTED_RETRANSMISSION = 2,
+ STATUS_ARRIVED_LATE = 4,
+ };
+#if TRACK_PACKET_LOSS
+ KeyedVector<int32_t, uint32_t> mLostPackets;
+#endif
+
+ void modifyPacketStatus(int32_t extSeqNo, uint32_t mask);
+
+ int32_t mAwaitingExtSeqNo;
+ bool mRequestedRetransmission;
+
+ int32_t mActivePacketType;
+ sp<Assembler> mActiveAssembler;
+
+ int64_t mNextReportTimeUs;
+
+ int32_t mNumDeclaredLost;
+ int32_t mNumDeclaredLostPrior;
+
+ int32_t mRetransmitGeneration;
+ int32_t mDeclareLostGeneration;
+ bool mDeclareLostTimerPending;
+
+ void queuePacket(const sp<ABuffer> &packet);
+ void dequeueMore();
+
+ sp<ABuffer> getNextPacket();
+ void resync();
+
+ void postRetransmitTimer(int64_t delayUs);
+ void postDeclareLostTimer(int64_t delayUs);
+ void cancelTimers();
+
+ DISALLOW_EVIL_CONSTRUCTORS(Source);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+RTPReceiver::Source::Source(RTPReceiver *receiver, uint32_t ssrc)
+ : mReceiver(receiver),
+ mSSRC(ssrc),
+ mFirst(true),
+ mMaxSeq(0),
+ mCycles(0),
+ mBaseSeq(0),
+ mReceived(0),
+ mExpectedPrior(0),
+ mReceivedPrior(0),
+ mFirstArrivalTimeUs(-1ll),
+ mFirstRTPTimeUs(-1ll),
+ mAwaitingExtSeqNo(-1),
+ mRequestedRetransmission(false),
+ mActivePacketType(-1),
+ mNextReportTimeUs(-1ll),
+ mNumDeclaredLost(0),
+ mNumDeclaredLostPrior(0),
+ mRetransmitGeneration(0),
+ mDeclareLostGeneration(0),
+ mDeclareLostTimerPending(false) {
+}
+
+RTPReceiver::Source::~Source() {
+}
+
+void RTPReceiver::Source::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRetransmit:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mRetransmitGeneration) {
+ break;
+ }
+
+ mRequestedRetransmission = true;
+ mReceiver->requestRetransmission(mSSRC, mAwaitingExtSeqNo);
+
+ modifyPacketStatus(
+ mAwaitingExtSeqNo, STATUS_REQUESTED_RETRANSMISSION);
+ break;
+ }
+
+ case kWhatDeclareLost:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mDeclareLostGeneration) {
+ break;
+ }
+
+ cancelTimers();
+
+ ALOGV("Lost packet extSeqNo %d %s",
+ mAwaitingExtSeqNo,
+ mRequestedRetransmission ? "*" : "");
+
+ mRequestedRetransmission = false;
+ if (mActiveAssembler != NULL) {
+ mActiveAssembler->signalDiscontinuity();
+ }
+
+ modifyPacketStatus(mAwaitingExtSeqNo, STATUS_DECLARED_LOST);
+
+ // resync();
+ ++mAwaitingExtSeqNo;
+ ++mNumDeclaredLost;
+
+ mReceiver->notifyPacketLost();
+
+ dequeueMore();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void RTPReceiver::Source::onPacketReceived(
+ uint16_t seq, const sp<ABuffer> &buffer) {
+ if (mFirst) {
+ buffer->setInt32Data(mCycles | seq);
+ queuePacket(buffer);
+
+ mFirst = false;
+ mBaseSeq = seq;
+ mMaxSeq = seq;
+ ++mReceived;
+ return;
+ }
+
+ uint16_t udelta = seq - mMaxSeq;
+
+ if (udelta < kMaxDropout) {
+ // In order, with permissible gap.
+
+ if (seq < mMaxSeq) {
+ // Sequence number wrapped - count another 64K cycle
+ mCycles += kRTPSeqMod;
+ }
+
+ mMaxSeq = seq;
+
+ ++mReceived;
+ } else if (udelta <= kRTPSeqMod - kMaxMisorder) {
+ // The sequence number made a very large jump
+ return;
+ } else {
+ // Duplicate or reordered packet.
+ }
+
+ buffer->setInt32Data(mCycles | seq);
+ queuePacket(buffer);
+}
+
+void RTPReceiver::Source::queuePacket(const sp<ABuffer> &packet) {
+ int32_t newExtendedSeqNo = packet->int32Data();
+
+ if (mFirstArrivalTimeUs < 0ll) {
+ mFirstArrivalTimeUs = ALooper::GetNowUs();
+
+ uint32_t rtpTime;
+ CHECK(packet->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ mFirstRTPTimeUs = (rtpTime * 100ll) / 9ll;
+ }
+
+ if (mAwaitingExtSeqNo >= 0 && newExtendedSeqNo < mAwaitingExtSeqNo) {
+ // We're no longer interested in these. They're old.
+ ALOGV("dropping stale extSeqNo %d", newExtendedSeqNo);
+
+ modifyPacketStatus(newExtendedSeqNo, STATUS_ARRIVED_LATE);
+ return;
+ }
+
+ if (mPackets.empty()) {
+ mPackets.push_back(packet);
+ dequeueMore();
+ return;
+ }
+
+ List<sp<ABuffer> >::iterator firstIt = mPackets.begin();
+ List<sp<ABuffer> >::iterator it = --mPackets.end();
+ for (;;) {
+ int32_t extendedSeqNo = (*it)->int32Data();
+
+ if (extendedSeqNo == newExtendedSeqNo) {
+ // Duplicate packet.
+ return;
+ }
+
+ if (extendedSeqNo < newExtendedSeqNo) {
+ // Insert new packet after the one at "it".
+ mPackets.insert(++it, packet);
+ break;
+ }
+
+ if (it == firstIt) {
+ // Insert new packet before the first existing one.
+ mPackets.insert(it, packet);
+ break;
+ }
+
+ --it;
+ }
+
+ dequeueMore();
+}
+
+void RTPReceiver::Source::dequeueMore() {
+ int64_t nowUs = ALooper::GetNowUs();
+ if (mNextReportTimeUs < 0ll || nowUs >= mNextReportTimeUs) {
+ if (mNextReportTimeUs >= 0ll) {
+ uint32_t expected = (mMaxSeq | mCycles) - mBaseSeq + 1;
+
+ uint32_t expectedInterval = expected - mExpectedPrior;
+ mExpectedPrior = expected;
+
+ uint32_t receivedInterval = mReceived - mReceivedPrior;
+ mReceivedPrior = mReceived;
+
+ int64_t lostInterval =
+ (int64_t)expectedInterval - (int64_t)receivedInterval;
+
+ int32_t declaredLostInterval =
+ mNumDeclaredLost - mNumDeclaredLostPrior;
+
+ mNumDeclaredLostPrior = mNumDeclaredLost;
+
+ if (declaredLostInterval > 0) {
+ ALOGI("lost %lld packets (%.2f %%), declared %d lost\n",
+ lostInterval,
+ 100.0f * lostInterval / expectedInterval,
+ declaredLostInterval);
+ }
+ }
+
+ mNextReportTimeUs = nowUs + kReportIntervalUs;
+
+#if TRACK_PACKET_LOSS
+ for (size_t i = 0; i < mLostPackets.size(); ++i) {
+ int32_t key = mLostPackets.keyAt(i);
+ uint32_t value = mLostPackets.valueAt(i);
+
+ AString status;
+ if (value & STATUS_REQUESTED_RETRANSMISSION) {
+ status.append("retrans ");
+ }
+ if (value & STATUS_ARRIVED_LATE) {
+ status.append("arrived-late ");
+ }
+ ALOGI("Packet %d declared lost %s", key, status.c_str());
+ }
+#endif
+ }
+
+ sp<ABuffer> packet;
+ while ((packet = getNextPacket()) != NULL) {
+ if (mDeclareLostTimerPending) {
+ cancelTimers();
+ }
+
+ CHECK_GE(mAwaitingExtSeqNo, 0);
+#if TRACK_PACKET_LOSS
+ mLostPackets.removeItem(mAwaitingExtSeqNo);
+#endif
+
+ int32_t packetType;
+ CHECK(packet->meta()->findInt32("PT", &packetType));
+
+ if (packetType != mActivePacketType) {
+ mActiveAssembler = mReceiver->makeAssembler(packetType);
+ mActivePacketType = packetType;
+ }
+
+ if (mActiveAssembler != NULL) {
+ status_t err = mActiveAssembler->processPacket(packet);
+ if (err != OK) {
+ ALOGV("assembler returned error %d", err);
+ }
+ }
+
+ ++mAwaitingExtSeqNo;
+ }
+
+ if (mDeclareLostTimerPending) {
+ return;
+ }
+
+ if (mPackets.empty()) {
+ return;
+ }
+
+ CHECK_GE(mAwaitingExtSeqNo, 0);
+
+ const sp<ABuffer> &firstPacket = *mPackets.begin();
+
+ uint32_t rtpTime;
+ CHECK(firstPacket->meta()->findInt32(
+ "rtp-time", (int32_t *)&rtpTime));
+
+
+ int64_t rtpUs = (rtpTime * 100ll) / 9ll;
+
+ int64_t maxArrivalTimeUs =
+ mFirstArrivalTimeUs + rtpUs - mFirstRTPTimeUs;
+
+ nowUs = ALooper::GetNowUs();
+
+ CHECK_LT(mAwaitingExtSeqNo, firstPacket->int32Data());
+
+ ALOGV("waiting for %d, comparing against %d, %lld us left",
+ mAwaitingExtSeqNo,
+ firstPacket->int32Data(),
+ maxArrivalTimeUs - nowUs);
+
+ postDeclareLostTimer(maxArrivalTimeUs + kPacketLostAfterUs);
+
+ if (kRequestRetransmissionAfterUs > 0ll) {
+ postRetransmitTimer(
+ maxArrivalTimeUs + kRequestRetransmissionAfterUs);
+ }
+}
+
+sp<ABuffer> RTPReceiver::Source::getNextPacket() {
+ if (mPackets.empty()) {
+ return NULL;
+ }
+
+ int32_t extSeqNo = (*mPackets.begin())->int32Data();
+
+ if (mAwaitingExtSeqNo < 0) {
+ mAwaitingExtSeqNo = extSeqNo;
+ } else if (extSeqNo != mAwaitingExtSeqNo) {
+ return NULL;
+ }
+
+ sp<ABuffer> packet = *mPackets.begin();
+ mPackets.erase(mPackets.begin());
+
+ return packet;
+}
+
+void RTPReceiver::Source::resync() {
+ mAwaitingExtSeqNo = -1;
+}
+
+void RTPReceiver::Source::addReportBlock(
+ uint32_t ssrc, const sp<ABuffer> &buf) {
+ uint32_t extMaxSeq = mMaxSeq | mCycles;
+ uint32_t expected = extMaxSeq - mBaseSeq + 1;
+
+ int64_t lost = (int64_t)expected - (int64_t)mReceived;
+ if (lost > 0x7fffff) {
+ lost = 0x7fffff;
+ } else if (lost < -0x800000) {
+ lost = -0x800000;
+ }
+
+ uint32_t expectedInterval = expected - mExpectedPrior;
+ mExpectedPrior = expected;
+
+ uint32_t receivedInterval = mReceived - mReceivedPrior;
+ mReceivedPrior = mReceived;
+
+ int64_t lostInterval = expectedInterval - receivedInterval;
+
+ uint8_t fractionLost;
+ if (expectedInterval == 0 || lostInterval <=0) {
+ fractionLost = 0;
+ } else {
+ fractionLost = (lostInterval << 8) / expectedInterval;
+ }
+
+ uint8_t *ptr = buf->data() + buf->size();
+
+ ptr[0] = ssrc >> 24;
+ ptr[1] = (ssrc >> 16) & 0xff;
+ ptr[2] = (ssrc >> 8) & 0xff;
+ ptr[3] = ssrc & 0xff;
+
+ ptr[4] = fractionLost;
+
+ ptr[5] = (lost >> 16) & 0xff;
+ ptr[6] = (lost >> 8) & 0xff;
+ ptr[7] = lost & 0xff;
+
+ ptr[8] = extMaxSeq >> 24;
+ ptr[9] = (extMaxSeq >> 16) & 0xff;
+ ptr[10] = (extMaxSeq >> 8) & 0xff;
+ ptr[11] = extMaxSeq & 0xff;
+
+ // XXX TODO:
+
+ ptr[12] = 0x00; // interarrival jitter
+ ptr[13] = 0x00;
+ ptr[14] = 0x00;
+ ptr[15] = 0x00;
+
+ ptr[16] = 0x00; // last SR
+ ptr[17] = 0x00;
+ ptr[18] = 0x00;
+ ptr[19] = 0x00;
+
+ ptr[20] = 0x00; // delay since last SR
+ ptr[21] = 0x00;
+ ptr[22] = 0x00;
+ ptr[23] = 0x00;
+
+ buf->setRange(buf->offset(), buf->size() + 24);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+RTPReceiver::RTPReceiver(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify,
+ uint32_t flags)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mFlags(flags),
+ mRTPMode(TRANSPORT_UNDEFINED),
+ mRTCPMode(TRANSPORT_UNDEFINED),
+ mRTPSessionID(0),
+ mRTCPSessionID(0),
+ mRTPConnected(false),
+ mRTCPConnected(false),
+ mRTPClientSessionID(0),
+ mRTCPClientSessionID(0) {
+}
+
+RTPReceiver::~RTPReceiver() {
+ if (mRTCPClientSessionID != 0) {
+ mNetSession->destroySession(mRTCPClientSessionID);
+ mRTCPClientSessionID = 0;
+ }
+
+ if (mRTPClientSessionID != 0) {
+ mNetSession->destroySession(mRTPClientSessionID);
+ mRTPClientSessionID = 0;
+ }
+
+ if (mRTCPSessionID != 0) {
+ mNetSession->destroySession(mRTCPSessionID);
+ mRTCPSessionID = 0;
+ }
+
+ if (mRTPSessionID != 0) {
+ mNetSession->destroySession(mRTPSessionID);
+ mRTPSessionID = 0;
+ }
+}
+
+status_t RTPReceiver::initAsync(
+ TransportMode rtpMode,
+ TransportMode rtcpMode,
+ int32_t *outLocalRTPPort) {
+ if (mRTPMode != TRANSPORT_UNDEFINED
+ || rtpMode == TRANSPORT_UNDEFINED
+ || rtpMode == TRANSPORT_NONE
+ || rtcpMode == TRANSPORT_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ CHECK_NE(rtpMode, TRANSPORT_TCP_INTERLEAVED);
+ CHECK_NE(rtcpMode, TRANSPORT_TCP_INTERLEAVED);
+
+ sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, id());
+
+ sp<AMessage> rtcpNotify;
+ if (rtcpMode != TRANSPORT_NONE) {
+ rtcpNotify = new AMessage(kWhatRTCPNotify, id());
+ }
+
+ CHECK_EQ(mRTPSessionID, 0);
+ CHECK_EQ(mRTCPSessionID, 0);
+
+ int32_t localRTPPort;
+
+ struct in_addr ifaceAddr;
+ ifaceAddr.s_addr = INADDR_ANY;
+
+ for (;;) {
+ localRTPPort = PickRandomRTPPort();
+
+ status_t err;
+ if (rtpMode == TRANSPORT_UDP) {
+ err = mNetSession->createUDPSession(
+ localRTPPort,
+ rtpNotify,
+ &mRTPSessionID);
+ } else {
+ CHECK_EQ(rtpMode, TRANSPORT_TCP);
+ err = mNetSession->createTCPDatagramSession(
+ ifaceAddr,
+ localRTPPort,
+ rtpNotify,
+ &mRTPSessionID);
+ }
+
+ if (err != OK) {
+ continue;
+ }
+
+ if (rtcpMode == TRANSPORT_NONE) {
+ break;
+ } else if (rtcpMode == TRANSPORT_UDP) {
+ err = mNetSession->createUDPSession(
+ localRTPPort + 1,
+ rtcpNotify,
+ &mRTCPSessionID);
+ } else {
+ CHECK_EQ(rtpMode, TRANSPORT_TCP);
+ err = mNetSession->createTCPDatagramSession(
+ ifaceAddr,
+ localRTPPort + 1,
+ rtcpNotify,
+ &mRTCPSessionID);
+ }
+
+ if (err == OK) {
+ break;
+ }
+
+ mNetSession->destroySession(mRTPSessionID);
+ mRTPSessionID = 0;
+ }
+
+ mRTPMode = rtpMode;
+ mRTCPMode = rtcpMode;
+ *outLocalRTPPort = localRTPPort;
+
+ return OK;
+}
+
+status_t RTPReceiver::connect(
+ const char *remoteHost, int32_t remoteRTPPort, int32_t remoteRTCPPort) {
+ status_t err;
+
+ if (mRTPMode == TRANSPORT_UDP) {
+ CHECK(!mRTPConnected);
+
+ err = mNetSession->connectUDPSession(
+ mRTPSessionID, remoteHost, remoteRTPPort);
+
+ if (err != OK) {
+ notifyInitDone(err);
+ return err;
+ }
+
+ ALOGI("connectUDPSession RTP successful.");
+
+ mRTPConnected = true;
+ }
+
+ if (mRTCPMode == TRANSPORT_UDP) {
+ CHECK(!mRTCPConnected);
+
+ err = mNetSession->connectUDPSession(
+ mRTCPSessionID, remoteHost, remoteRTCPPort);
+
+ if (err != OK) {
+ notifyInitDone(err);
+ return err;
+ }
+
+ scheduleSendRR();
+
+ ALOGI("connectUDPSession RTCP successful.");
+
+ mRTCPConnected = true;
+ }
+
+ if (mRTPConnected
+ && (mRTCPConnected || mRTCPMode == TRANSPORT_NONE)) {
+ notifyInitDone(OK);
+ }
+
+ return OK;
+}
+
+status_t RTPReceiver::informSender(const sp<AMessage> &params) {
+ if (!mRTCPConnected) {
+ return INVALID_OPERATION;
+ }
+
+ int64_t avgLatencyUs;
+ CHECK(params->findInt64("avgLatencyUs", &avgLatencyUs));
+
+ int64_t maxLatencyUs;
+ CHECK(params->findInt64("maxLatencyUs", &maxLatencyUs));
+
+ sp<ABuffer> buf = new ABuffer(28);
+
+ uint8_t *ptr = buf->data();
+ ptr[0] = 0x80 | 0;
+ ptr[1] = 204; // APP
+ ptr[2] = 0;
+
+ CHECK((buf->size() % 4) == 0u);
+ ptr[3] = (buf->size() / 4) - 1;
+
+ ptr[4] = kSourceID >> 24; // SSRC
+ ptr[5] = (kSourceID >> 16) & 0xff;
+ ptr[6] = (kSourceID >> 8) & 0xff;
+ ptr[7] = kSourceID & 0xff;
+ ptr[8] = 'l';
+ ptr[9] = 'a';
+ ptr[10] = 't';
+ ptr[11] = 'e';
+
+ ptr[12] = avgLatencyUs >> 56;
+ ptr[13] = (avgLatencyUs >> 48) & 0xff;
+ ptr[14] = (avgLatencyUs >> 40) & 0xff;
+ ptr[15] = (avgLatencyUs >> 32) & 0xff;
+ ptr[16] = (avgLatencyUs >> 24) & 0xff;
+ ptr[17] = (avgLatencyUs >> 16) & 0xff;
+ ptr[18] = (avgLatencyUs >> 8) & 0xff;
+ ptr[19] = avgLatencyUs & 0xff;
+
+ ptr[20] = maxLatencyUs >> 56;
+ ptr[21] = (maxLatencyUs >> 48) & 0xff;
+ ptr[22] = (maxLatencyUs >> 40) & 0xff;
+ ptr[23] = (maxLatencyUs >> 32) & 0xff;
+ ptr[24] = (maxLatencyUs >> 24) & 0xff;
+ ptr[25] = (maxLatencyUs >> 16) & 0xff;
+ ptr[26] = (maxLatencyUs >> 8) & 0xff;
+ ptr[27] = maxLatencyUs & 0xff;
+
+ mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
+
+ return OK;
+}
+
+void RTPReceiver::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRTPNotify:
+ case kWhatRTCPNotify:
+ onNetNotify(msg->what() == kWhatRTPNotify, msg);
+ break;
+
+ case kWhatSendRR:
+ {
+ onSendRR();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void RTPReceiver::onNetNotify(bool isRTP, const sp<AMessage> &msg) {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ int32_t errorOccuredDuringSend;
+ CHECK(msg->findInt32("send", &errorOccuredDuringSend));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred during %s in session %d "
+ "(%d, '%s' (%s)).",
+ errorOccuredDuringSend ? "send" : "receive",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ mNetSession->destroySession(sessionID);
+
+ if (sessionID == mRTPSessionID) {
+ mRTPSessionID = 0;
+ } else if (sessionID == mRTCPSessionID) {
+ mRTCPSessionID = 0;
+ } else if (sessionID == mRTPClientSessionID) {
+ mRTPClientSessionID = 0;
+ } else if (sessionID == mRTCPClientSessionID) {
+ mRTCPClientSessionID = 0;
+ }
+
+ if (!mRTPConnected
+ || (mRTCPMode != TRANSPORT_NONE && !mRTCPConnected)) {
+ notifyInitDone(err);
+ break;
+ }
+
+ notifyError(err);
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ sp<ABuffer> data;
+ CHECK(msg->findBuffer("data", &data));
+
+ if (isRTP) {
+ if (mFlags & FLAG_AUTO_CONNECT) {
+ AString fromAddr;
+ CHECK(msg->findString("fromAddr", &fromAddr));
+
+ int32_t fromPort;
+ CHECK(msg->findInt32("fromPort", &fromPort));
+
+ CHECK_EQ((status_t)OK,
+ connect(
+ fromAddr.c_str(), fromPort, fromPort + 1));
+
+ mFlags &= ~FLAG_AUTO_CONNECT;
+ }
+
+ onRTPData(data);
+ } else {
+ onRTCPData(data);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatClientConnected:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ if (isRTP) {
+ CHECK_EQ(mRTPMode, TRANSPORT_TCP);
+
+ if (mRTPClientSessionID != 0) {
+ // We only allow a single client connection.
+ mNetSession->destroySession(sessionID);
+ sessionID = 0;
+ break;
+ }
+
+ mRTPClientSessionID = sessionID;
+ mRTPConnected = true;
+ } else {
+ CHECK_EQ(mRTCPMode, TRANSPORT_TCP);
+
+ if (mRTCPClientSessionID != 0) {
+ // We only allow a single client connection.
+ mNetSession->destroySession(sessionID);
+ sessionID = 0;
+ break;
+ }
+
+ mRTCPClientSessionID = sessionID;
+ mRTCPConnected = true;
+ }
+
+ if (mRTPConnected
+ && (mRTCPConnected || mRTCPMode == TRANSPORT_NONE)) {
+ notifyInitDone(OK);
+ }
+ break;
+ }
+ }
+}
+
+void RTPReceiver::notifyInitDone(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInitDone);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void RTPReceiver::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void RTPReceiver::notifyPacketLost() {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatPacketLost);
+ notify->post();
+}
+
+status_t RTPReceiver::onRTPData(const sp<ABuffer> &buffer) {
+ size_t size = buffer->size();
+ if (size < 12) {
+ // Too short to be a valid RTP header.
+ return ERROR_MALFORMED;
+ }
+
+ const uint8_t *data = buffer->data();
+
+ if ((data[0] >> 6) != 2) {
+ // Unsupported version.
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (data[0] & 0x20) {
+ // Padding present.
+
+ size_t paddingLength = data[size - 1];
+
+ if (paddingLength + 12 > size) {
+ // If we removed this much padding we'd end up with something
+ // that's too short to be a valid RTP header.
+ return ERROR_MALFORMED;
+ }
+
+ size -= paddingLength;
+ }
+
+ int numCSRCs = data[0] & 0x0f;
+
+ size_t payloadOffset = 12 + 4 * numCSRCs;
+
+ if (size < payloadOffset) {
+ // Not enough data to fit the basic header and all the CSRC entries.
+ return ERROR_MALFORMED;
+ }
+
+ if (data[0] & 0x10) {
+ // Header eXtension present.
+
+ if (size < payloadOffset + 4) {
+ // Not enough data to fit the basic header, all CSRC entries
+ // and the first 4 bytes of the extension header.
+
+ return ERROR_MALFORMED;
+ }
+
+ const uint8_t *extensionData = &data[payloadOffset];
+
+ size_t extensionLength =
+ 4 * (extensionData[2] << 8 | extensionData[3]);
+
+ if (size < payloadOffset + 4 + extensionLength) {
+ return ERROR_MALFORMED;
+ }
+
+ payloadOffset += 4 + extensionLength;
+ }
+
+ uint32_t srcId = U32_AT(&data[8]);
+ uint32_t rtpTime = U32_AT(&data[4]);
+ uint16_t seqNo = U16_AT(&data[2]);
+
+ sp<AMessage> meta = buffer->meta();
+ meta->setInt32("ssrc", srcId);
+ meta->setInt32("rtp-time", rtpTime);
+ meta->setInt32("PT", data[1] & 0x7f);
+ meta->setInt32("M", data[1] >> 7);
+
+ buffer->setRange(payloadOffset, size - payloadOffset);
+
+ ssize_t index = mSources.indexOfKey(srcId);
+ sp<Source> source;
+ if (index < 0) {
+ source = new Source(this, srcId);
+ looper()->registerHandler(source);
+
+ mSources.add(srcId, source);
+ } else {
+ source = mSources.valueAt(index);
+ }
+
+ source->onPacketReceived(seqNo, buffer);
+
+ return OK;
+}
+
+status_t RTPReceiver::onRTCPData(const sp<ABuffer> &data) {
+ ALOGI("onRTCPData");
+ return OK;
+}
+
+void RTPReceiver::addSDES(const sp<ABuffer> &buffer) {
+ uint8_t *data = buffer->data() + buffer->size();
+ data[0] = 0x80 | 1;
+ data[1] = 202; // SDES
+ data[4] = kSourceID >> 24; // SSRC
+ data[5] = (kSourceID >> 16) & 0xff;
+ data[6] = (kSourceID >> 8) & 0xff;
+ data[7] = kSourceID & 0xff;
+
+ size_t offset = 8;
+
+ data[offset++] = 1; // CNAME
+
+ AString cname = "stagefright@somewhere";
+ data[offset++] = cname.size();
+
+ memcpy(&data[offset], cname.c_str(), cname.size());
+ offset += cname.size();
+
+ data[offset++] = 6; // TOOL
+
+ AString tool = "stagefright/1.0";
+ data[offset++] = tool.size();
+
+ memcpy(&data[offset], tool.c_str(), tool.size());
+ offset += tool.size();
+
+ data[offset++] = 0;
+
+ if ((offset % 4) > 0) {
+ size_t count = 4 - (offset % 4);
+ switch (count) {
+ case 3:
+ data[offset++] = 0;
+ case 2:
+ data[offset++] = 0;
+ case 1:
+ data[offset++] = 0;
+ }
+ }
+
+ size_t numWords = (offset / 4) - 1;
+ data[2] = numWords >> 8;
+ data[3] = numWords & 0xff;
+
+ buffer->setRange(buffer->offset(), buffer->size() + offset);
+}
+
+void RTPReceiver::scheduleSendRR() {
+ (new AMessage(kWhatSendRR, id()))->post(5000000ll);
+}
+
+void RTPReceiver::onSendRR() {
+ sp<ABuffer> buf = new ABuffer(kMaxUDPPacketSize);
+ buf->setRange(0, 0);
+
+ uint8_t *ptr = buf->data();
+ ptr[0] = 0x80 | 0;
+ ptr[1] = 201; // RR
+ ptr[2] = 0;
+ ptr[3] = 1;
+ ptr[4] = kSourceID >> 24; // SSRC
+ ptr[5] = (kSourceID >> 16) & 0xff;
+ ptr[6] = (kSourceID >> 8) & 0xff;
+ ptr[7] = kSourceID & 0xff;
+
+ buf->setRange(0, 8);
+
+ size_t numReportBlocks = 0;
+ for (size_t i = 0; i < mSources.size(); ++i) {
+ uint32_t ssrc = mSources.keyAt(i);
+ sp<Source> source = mSources.valueAt(i);
+
+ if (numReportBlocks > 31 || buf->size() + 24 > buf->capacity()) {
+ // Cannot fit another report block.
+ break;
+ }
+
+ source->addReportBlock(ssrc, buf);
+ ++numReportBlocks;
+ }
+
+ ptr[0] |= numReportBlocks; // 5 bit
+
+ size_t sizeInWordsMinus1 = 1 + 6 * numReportBlocks;
+ ptr[2] = sizeInWordsMinus1 >> 8;
+ ptr[3] = sizeInWordsMinus1 & 0xff;
+
+ buf->setRange(0, (sizeInWordsMinus1 + 1) * 4);
+
+ addSDES(buf);
+
+ mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
+
+ scheduleSendRR();
+}
+
+status_t RTPReceiver::registerPacketType(
+ uint8_t packetType, PacketizationMode mode) {
+ mPacketTypes.add(packetType, mode);
+
+ return OK;
+}
+
+sp<RTPReceiver::Assembler> RTPReceiver::makeAssembler(uint8_t packetType) {
+ ssize_t index = mPacketTypes.indexOfKey(packetType);
+ if (index < 0) {
+ return NULL;
+ }
+
+ PacketizationMode mode = mPacketTypes.valueAt(index);
+
+ switch (mode) {
+ case PACKETIZATION_NONE:
+ case PACKETIZATION_TRANSPORT_STREAM:
+ return new TSAssembler(mNotify);
+
+ case PACKETIZATION_H264:
+ return new H264Assembler(mNotify);
+
+ default:
+ return NULL;
+ }
+}
+
+void RTPReceiver::requestRetransmission(uint32_t senderSSRC, int32_t extSeqNo) {
+ int32_t blp = 0;
+
+ sp<ABuffer> buf = new ABuffer(16);
+ buf->setRange(0, 0);
+
+ uint8_t *ptr = buf->data();
+ ptr[0] = 0x80 | 1; // generic NACK
+ ptr[1] = 205; // TSFB
+ ptr[2] = 0;
+ ptr[3] = 3;
+ ptr[8] = (senderSSRC >> 24) & 0xff;
+ ptr[9] = (senderSSRC >> 16) & 0xff;
+ ptr[10] = (senderSSRC >> 8) & 0xff;
+ ptr[11] = (senderSSRC & 0xff);
+ ptr[8] = (kSourceID >> 24) & 0xff;
+ ptr[9] = (kSourceID >> 16) & 0xff;
+ ptr[10] = (kSourceID >> 8) & 0xff;
+ ptr[11] = (kSourceID & 0xff);
+ ptr[12] = (extSeqNo >> 8) & 0xff;
+ ptr[13] = (extSeqNo & 0xff);
+ ptr[14] = (blp >> 8) & 0xff;
+ ptr[15] = (blp & 0xff);
+
+ buf->setRange(0, 16);
+
+ mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
+}
+
+void RTPReceiver::Source::modifyPacketStatus(int32_t extSeqNo, uint32_t mask) {
+#if TRACK_PACKET_LOSS
+ ssize_t index = mLostPackets.indexOfKey(extSeqNo);
+ if (index < 0) {
+ mLostPackets.add(extSeqNo, mask);
+ } else {
+ mLostPackets.editValueAt(index) |= mask;
+ }
+#endif
+}
+
+void RTPReceiver::Source::postRetransmitTimer(int64_t timeUs) {
+ int64_t delayUs = timeUs - ALooper::GetNowUs();
+ sp<AMessage> msg = new AMessage(kWhatRetransmit, id());
+ msg->setInt32("generation", mRetransmitGeneration);
+ msg->post(delayUs);
+}
+
+void RTPReceiver::Source::postDeclareLostTimer(int64_t timeUs) {
+ CHECK(!mDeclareLostTimerPending);
+ mDeclareLostTimerPending = true;
+
+ int64_t delayUs = timeUs - ALooper::GetNowUs();
+ sp<AMessage> msg = new AMessage(kWhatDeclareLost, id());
+ msg->setInt32("generation", mDeclareLostGeneration);
+ msg->post(delayUs);
+}
+
+void RTPReceiver::Source::cancelTimers() {
+ ++mRetransmitGeneration;
+ ++mDeclareLostGeneration;
+ mDeclareLostTimerPending = false;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.h b/media/libstagefright/wifi-display/rtp/RTPReceiver.h
new file mode 100644
index 0000000..240ab2e
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.h
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+#ifndef RTP_RECEIVER_H_
+
+#define RTP_RECEIVER_H_
+
+#include "RTPBase.h"
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+struct ANetworkSession;
+
+// An object of this class facilitates receiving of media data on an RTP
+// channel. The channel is established over a UDP or TCP connection depending
+// on which "TransportMode" was chosen. In addition different RTP packetization
+// schemes are supported such as "Transport Stream Packets over RTP",
+// or "AVC/H.264 encapsulation as specified in RFC 3984 (non-interleaved mode)"
+struct RTPReceiver : public RTPBase, public AHandler {
+ enum {
+ kWhatInitDone,
+ kWhatError,
+ kWhatAccessUnit,
+ kWhatPacketLost,
+ };
+
+ enum Flags {
+ FLAG_AUTO_CONNECT = 1,
+ };
+ RTPReceiver(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> &notify,
+ uint32_t flags = 0);
+
+ status_t registerPacketType(
+ uint8_t packetType, PacketizationMode mode);
+
+ status_t initAsync(
+ TransportMode rtpMode,
+ TransportMode rtcpMode,
+ int32_t *outLocalRTPPort);
+
+ status_t connect(
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ int32_t remoteRTCPPort);
+
+ status_t informSender(const sp<AMessage> &params);
+
+protected:
+ virtual ~RTPReceiver();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatRTPNotify,
+ kWhatRTCPNotify,
+ kWhatSendRR,
+ };
+
+ enum {
+ kSourceID = 0xdeadbeef,
+ kPacketLostAfterUs = 100000,
+ kRequestRetransmissionAfterUs = -1,
+ };
+
+ struct Assembler;
+ struct H264Assembler;
+ struct Source;
+ struct TSAssembler;
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+ uint32_t mFlags;
+ TransportMode mRTPMode;
+ TransportMode mRTCPMode;
+ int32_t mRTPSessionID;
+ int32_t mRTCPSessionID;
+ bool mRTPConnected;
+ bool mRTCPConnected;
+
+ int32_t mRTPClientSessionID; // in TRANSPORT_TCP mode.
+ int32_t mRTCPClientSessionID; // in TRANSPORT_TCP mode.
+
+ KeyedVector<uint8_t, PacketizationMode> mPacketTypes;
+ KeyedVector<uint32_t, sp<Source> > mSources;
+
+ void onNetNotify(bool isRTP, const sp<AMessage> &msg);
+ status_t onRTPData(const sp<ABuffer> &data);
+ status_t onRTCPData(const sp<ABuffer> &data);
+ void onSendRR();
+
+ void scheduleSendRR();
+ void addSDES(const sp<ABuffer> &buffer);
+
+ void notifyInitDone(status_t err);
+ void notifyError(status_t err);
+ void notifyPacketLost();
+
+ sp<Assembler> makeAssembler(uint8_t packetType);
+
+ void requestRetransmission(uint32_t senderSSRC, int32_t extSeqNo);
+
+ DISALLOW_EVIL_CONSTRUCTORS(RTPReceiver);
+};
+
+} // namespace android
+
+#endif // RTP_RECEIVER_H_
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/rtptest.cpp b/media/libstagefright/wifi-display/rtptest.cpp
new file mode 100644
index 0000000..b902f29
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtptest.cpp
@@ -0,0 +1,565 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NEBUG 0
+#define LOG_TAG "rtptest"
+#include <utils/Log.h>
+
+#include "rtp/RTPSender.h"
+#include "rtp/RTPReceiver.h"
+#include "TimeSyncer.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/Utils.h>
+
+#define MEDIA_FILENAME "/sdcard/Frame Counter HD 30FPS_1080p.mp4"
+
+namespace android {
+
+struct PacketSource : public RefBase {
+ PacketSource() {}
+
+ virtual sp<ABuffer> getNextAccessUnit() = 0;
+
+protected:
+ virtual ~PacketSource() {}
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(PacketSource);
+};
+
+struct MediaPacketSource : public PacketSource {
+ MediaPacketSource()
+ : mMaxSampleSize(1024 * 1024) {
+ mExtractor = new NuMediaExtractor;
+ CHECK_EQ((status_t)OK,
+ mExtractor->setDataSource(MEDIA_FILENAME));
+
+ bool haveVideo = false;
+ for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
+ sp<AMessage> format;
+ CHECK_EQ((status_t)OK, mExtractor->getTrackFormat(i, &format));
+
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str())) {
+ mExtractor->selectTrack(i);
+ haveVideo = true;
+ break;
+ }
+ }
+
+ CHECK(haveVideo);
+ }
+
+ virtual sp<ABuffer> getNextAccessUnit() {
+ int64_t timeUs;
+ status_t err = mExtractor->getSampleTime(&timeUs);
+
+ if (err != OK) {
+ return NULL;
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(mMaxSampleSize);
+ CHECK_EQ((status_t)OK, mExtractor->readSampleData(accessUnit));
+
+ accessUnit->meta()->setInt64("timeUs", timeUs);
+
+ CHECK_EQ((status_t)OK, mExtractor->advance());
+
+ return accessUnit;
+ }
+
+protected:
+ virtual ~MediaPacketSource() {
+ }
+
+private:
+ sp<NuMediaExtractor> mExtractor;
+ size_t mMaxSampleSize;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaPacketSource);
+};
+
+struct SimplePacketSource : public PacketSource {
+ SimplePacketSource()
+ : mCounter(0) {
+ }
+
+ virtual sp<ABuffer> getNextAccessUnit() {
+ sp<ABuffer> buffer = new ABuffer(4);
+ uint8_t *dst = buffer->data();
+ dst[0] = mCounter >> 24;
+ dst[1] = (mCounter >> 16) & 0xff;
+ dst[2] = (mCounter >> 8) & 0xff;
+ dst[3] = mCounter & 0xff;
+
+ buffer->meta()->setInt64("timeUs", mCounter * 1000000ll / kFrameRate);
+
+ ++mCounter;
+
+ return buffer;
+ }
+
+protected:
+ virtual ~SimplePacketSource() {
+ }
+
+private:
+ enum {
+ kFrameRate = 30
+ };
+
+ uint32_t mCounter;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SimplePacketSource);
+};
+
+struct TestHandler : public AHandler {
+ TestHandler(const sp<ANetworkSession> &netSession);
+
+ void listen();
+ void connect(const char *host, int32_t port);
+
+protected:
+ virtual ~TestHandler();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatListen,
+ kWhatConnect,
+ kWhatReceiverNotify,
+ kWhatSenderNotify,
+ kWhatSendMore,
+ kWhatStop,
+ kWhatTimeSyncerNotify,
+ };
+
+#if 1
+ static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_UDP;
+ static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_UDP;
+#else
+ static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_TCP;
+ static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_NONE;
+#endif
+
+#if 1
+ static const RTPBase::PacketizationMode kPacketizationMode
+ = RTPBase::PACKETIZATION_H264;
+#else
+ static const RTPBase::PacketizationMode kPacketizationMode
+ = RTPBase::PACKETIZATION_NONE;
+#endif
+
+ sp<ANetworkSession> mNetSession;
+ sp<PacketSource> mSource;
+ sp<RTPSender> mSender;
+ sp<RTPReceiver> mReceiver;
+
+ sp<TimeSyncer> mTimeSyncer;
+ bool mTimeSyncerStarted;
+
+ int64_t mFirstTimeRealUs;
+ int64_t mFirstTimeMediaUs;
+
+ int64_t mTimeOffsetUs;
+ bool mTimeOffsetValid;
+
+ status_t readMore();
+
+ DISALLOW_EVIL_CONSTRUCTORS(TestHandler);
+};
+
+TestHandler::TestHandler(const sp<ANetworkSession> &netSession)
+ : mNetSession(netSession),
+ mTimeSyncerStarted(false),
+ mFirstTimeRealUs(-1ll),
+ mFirstTimeMediaUs(-1ll),
+ mTimeOffsetUs(-1ll),
+ mTimeOffsetValid(false) {
+}
+
+TestHandler::~TestHandler() {
+}
+
+void TestHandler::listen() {
+ sp<AMessage> msg = new AMessage(kWhatListen, id());
+ msg->post();
+}
+
+void TestHandler::connect(const char *host, int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatConnect, id());
+ msg->setString("host", host);
+ msg->setInt32("port", port);
+ msg->post();
+}
+
+static void dumpDelay(int64_t delayMs) {
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ ALOGI("(%4lld ms) %s\n",
+ delayMs,
+ kPattern + kPatternSize - n);
+}
+
+void TestHandler::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatListen:
+ {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ notify = new AMessage(kWhatReceiverNotify, id());
+ mReceiver = new RTPReceiver(
+ mNetSession, notify, RTPReceiver::FLAG_AUTO_CONNECT);
+ looper()->registerHandler(mReceiver);
+
+ CHECK_EQ((status_t)OK,
+ mReceiver->registerPacketType(33, kPacketizationMode));
+
+ int32_t receiverRTPPort;
+ CHECK_EQ((status_t)OK,
+ mReceiver->initAsync(
+ kRTPMode,
+ kRTCPMode,
+ &receiverRTPPort));
+
+ printf("picked receiverRTPPort %d\n", receiverRTPPort);
+
+#if 0
+ CHECK_EQ((status_t)OK,
+ mReceiver->connect(
+ "127.0.0.1", senderRTPPort, senderRTPPort + 1));
+#endif
+ break;
+ }
+
+ case kWhatConnect:
+ {
+ AString host;
+ CHECK(msg->findString("host", &host));
+
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+ mTimeSyncer->startServer(8123);
+
+ int32_t receiverRTPPort;
+ CHECK(msg->findInt32("port", &receiverRTPPort));
+
+#if 1
+ mSource = new MediaPacketSource;
+#else
+ mSource = new SimplePacketSource;
+#endif
+
+ notify = new AMessage(kWhatSenderNotify, id());
+ mSender = new RTPSender(mNetSession, notify);
+
+ looper()->registerHandler(mSender);
+
+ int32_t senderRTPPort;
+ CHECK_EQ((status_t)OK,
+ mSender->initAsync(
+ host.c_str(),
+ receiverRTPPort,
+ kRTPMode,
+ kRTCPMode == RTPBase::TRANSPORT_NONE
+ ? -1 : receiverRTPPort + 1,
+ kRTCPMode,
+ &senderRTPPort));
+
+ printf("picked senderRTPPort %d\n", senderRTPPort);
+ break;
+ }
+
+ case kWhatSenderNotify:
+ {
+ ALOGI("kWhatSenderNotify");
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case RTPSender::kWhatInitDone:
+ {
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGI("RTPSender::initAsync completed w/ err %d", err);
+
+ if (err == OK) {
+ err = readMore();
+
+ if (err != OK) {
+ (new AMessage(kWhatStop, id()))->post();
+ }
+ }
+ break;
+ }
+
+ case RTPSender::kWhatError:
+ break;
+ }
+ break;
+ }
+
+ case kWhatReceiverNotify:
+ {
+ ALOGV("kWhatReceiverNotify");
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case RTPReceiver::kWhatInitDone:
+ {
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGI("RTPReceiver::initAsync completed w/ err %d", err);
+ break;
+ }
+
+ case RTPReceiver::kWhatError:
+ break;
+
+ case RTPReceiver::kWhatAccessUnit:
+ {
+#if 0
+ if (!mTimeSyncerStarted) {
+ mTimeSyncer->startClient("172.18.41.216", 8123);
+ mTimeSyncerStarted = true;
+ }
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ if (mTimeOffsetValid) {
+ timeUs -= mTimeOffsetUs;
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayMs = (nowUs - timeUs) / 1000ll;
+
+ dumpDelay(delayMs);
+ }
+#endif
+ break;
+ }
+
+ case RTPReceiver::kWhatPacketLost:
+ ALOGV("kWhatPacketLost");
+ break;
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatSendMore:
+ {
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ CHECK_EQ((status_t)OK,
+ mSender->queueBuffer(
+ accessUnit,
+ 33,
+ kPacketizationMode));
+
+ status_t err = readMore();
+
+ if (err != OK) {
+ (new AMessage(kWhatStop, id()))->post();
+ }
+ break;
+ }
+
+ case kWhatStop:
+ {
+ if (mReceiver != NULL) {
+ looper()->unregisterHandler(mReceiver->id());
+ mReceiver.clear();
+ }
+
+ if (mSender != NULL) {
+ looper()->unregisterHandler(mSender->id());
+ mSender.clear();
+ }
+
+ mSource.clear();
+
+ looper()->stop();
+ break;
+ }
+
+ case kWhatTimeSyncerNotify:
+ {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+status_t TestHandler::readMore() {
+ sp<ABuffer> accessUnit = mSource->getNextAccessUnit();
+
+ if (accessUnit == NULL) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t whenUs;
+
+ if (mFirstTimeRealUs < 0ll) {
+ mFirstTimeRealUs = whenUs = nowUs;
+ mFirstTimeMediaUs = timeUs;
+ } else {
+ whenUs = mFirstTimeRealUs + timeUs - mFirstTimeMediaUs;
+ }
+
+ accessUnit->meta()->setInt64("timeUs", whenUs);
+
+ sp<AMessage> msg = new AMessage(kWhatSendMore, id());
+ msg->setBuffer("accessUnit", accessUnit);
+ msg->post(whenUs - nowUs);
+
+ return OK;
+}
+
+} // namespace android
+
+static void usage(const char *me) {
+ fprintf(stderr,
+ "usage: %s -c host:port\tconnect to remote host\n"
+ " -l \tlisten\n",
+ me);
+}
+
+int main(int argc, char **argv) {
+ using namespace android;
+
+ // srand(time(NULL));
+
+ ProcessState::self()->startThreadPool();
+
+ DataSource::RegisterDefaultSniffers();
+
+ bool listen = false;
+ int32_t connectToPort = -1;
+ AString connectToHost;
+
+ int res;
+ while ((res = getopt(argc, argv, "hc:l")) >= 0) {
+ switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 1 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case 'l':
+ {
+ listen = true;
+ break;
+ }
+
+ case '?':
+ case 'h':
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if (!listen && connectToPort < 0) {
+ fprintf(stderr,
+ "You need to select either client or server mode.\n");
+ exit(1);
+ }
+
+ sp<ANetworkSession> netSession = new ANetworkSession;
+ netSession->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<TestHandler> handler = new TestHandler(netSession);
+ looper->registerHandler(handler);
+
+ if (listen) {
+ handler->listen();
+ }
+
+ if (connectToPort >= 0) {
+ handler->connect(connectToHost.c_str(), connectToPort);
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ return 0;
+}
+
diff --git a/media/libstagefright/wifi-display/sink/DirectRenderer.cpp b/media/libstagefright/wifi-display/sink/DirectRenderer.cpp
new file mode 100644
index 0000000..cdb2267
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/DirectRenderer.cpp
@@ -0,0 +1,653 @@
+/*
+ * 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 "DirectRenderer"
+#include <utils/Log.h>
+
+#include "DirectRenderer.h"
+
+#include <gui/SurfaceComposerClient.h>
+#include <gui/Surface.h>
+#include <media/AudioTrack.h>
+#include <media/ICrypto.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+/*
+ Drives the decoding process using a MediaCodec instance. Input buffers
+ queued by calls to "queueInputBuffer" are fed to the decoder as soon
+ as the decoder is ready for them, the client is notified about output
+ buffers as the decoder spits them out.
+*/
+struct DirectRenderer::DecoderContext : public AHandler {
+ enum {
+ kWhatOutputBufferReady,
+ };
+ DecoderContext(const sp<AMessage> &notify);
+
+ status_t init(
+ const sp<AMessage> &format,
+ const sp<IGraphicBufferProducer> &surfaceTex);
+
+ void queueInputBuffer(const sp<ABuffer> &accessUnit);
+
+ status_t renderOutputBufferAndRelease(size_t index);
+ status_t releaseOutputBuffer(size_t index);
+
+protected:
+ virtual ~DecoderContext();
+
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatDecoderNotify,
+ };
+
+ sp<AMessage> mNotify;
+ sp<ALooper> mDecoderLooper;
+ sp<MediaCodec> mDecoder;
+ Vector<sp<ABuffer> > mDecoderInputBuffers;
+ Vector<sp<ABuffer> > mDecoderOutputBuffers;
+ List<size_t> mDecoderInputBuffersAvailable;
+ bool mDecoderNotificationPending;
+
+ List<sp<ABuffer> > mAccessUnits;
+
+ void onDecoderNotify();
+ void scheduleDecoderNotification();
+ void queueDecoderInputBuffers();
+
+ void queueOutputBuffer(
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer);
+
+ DISALLOW_EVIL_CONSTRUCTORS(DecoderContext);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*
+ A "push" audio renderer. The primary function of this renderer is to use
+ an AudioTrack in push mode and making sure not to block the event loop
+ be ensuring that calls to AudioTrack::write never block. This is done by
+ estimating an upper bound of data that can be written to the AudioTrack
+ buffer without delay.
+*/
+struct DirectRenderer::AudioRenderer : public AHandler {
+ AudioRenderer(const sp<DecoderContext> &decoderContext);
+
+ void queueInputBuffer(
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer);
+
+protected:
+ virtual ~AudioRenderer();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatPushAudio,
+ };
+
+ struct BufferInfo {
+ size_t mIndex;
+ int64_t mTimeUs;
+ sp<ABuffer> mBuffer;
+ };
+
+ sp<DecoderContext> mDecoderContext;
+ sp<AudioTrack> mAudioTrack;
+
+ List<BufferInfo> mInputBuffers;
+ bool mPushPending;
+
+ size_t mNumFramesWritten;
+
+ void schedulePushIfNecessary();
+ void onPushAudio();
+
+ ssize_t writeNonBlocking(const uint8_t *data, size_t size);
+
+ DISALLOW_EVIL_CONSTRUCTORS(AudioRenderer);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+DirectRenderer::DecoderContext::DecoderContext(const sp<AMessage> &notify)
+ : mNotify(notify),
+ mDecoderNotificationPending(false) {
+}
+
+DirectRenderer::DecoderContext::~DecoderContext() {
+ if (mDecoder != NULL) {
+ mDecoder->release();
+ mDecoder.clear();
+
+ mDecoderLooper->stop();
+ mDecoderLooper.clear();
+ }
+}
+
+status_t DirectRenderer::DecoderContext::init(
+ const sp<AMessage> &format,
+ const sp<IGraphicBufferProducer> &surfaceTex) {
+ CHECK(mDecoder == NULL);
+
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ mDecoderLooper = new ALooper;
+ mDecoderLooper->setName("video codec looper");
+
+ mDecoderLooper->start(
+ false /* runOnCallingThread */,
+ false /* canCallJava */,
+ PRIORITY_DEFAULT);
+
+ mDecoder = MediaCodec::CreateByType(
+ mDecoderLooper, mime.c_str(), false /* encoder */);
+
+ CHECK(mDecoder != NULL);
+
+ status_t err = mDecoder->configure(
+ format,
+ surfaceTex == NULL
+ ? NULL : new Surface(surfaceTex),
+ NULL /* crypto */,
+ 0 /* flags */);
+ CHECK_EQ(err, (status_t)OK);
+
+ err = mDecoder->start();
+ CHECK_EQ(err, (status_t)OK);
+
+ err = mDecoder->getInputBuffers(
+ &mDecoderInputBuffers);
+ CHECK_EQ(err, (status_t)OK);
+
+ err = mDecoder->getOutputBuffers(
+ &mDecoderOutputBuffers);
+ CHECK_EQ(err, (status_t)OK);
+
+ scheduleDecoderNotification();
+
+ return OK;
+}
+
+void DirectRenderer::DecoderContext::queueInputBuffer(
+ const sp<ABuffer> &accessUnit) {
+ CHECK(mDecoder != NULL);
+
+ mAccessUnits.push_back(accessUnit);
+ queueDecoderInputBuffers();
+}
+
+status_t DirectRenderer::DecoderContext::renderOutputBufferAndRelease(
+ size_t index) {
+ return mDecoder->renderOutputBufferAndRelease(index);
+}
+
+status_t DirectRenderer::DecoderContext::releaseOutputBuffer(size_t index) {
+ return mDecoder->releaseOutputBuffer(index);
+}
+
+void DirectRenderer::DecoderContext::queueDecoderInputBuffers() {
+ if (mDecoder == NULL) {
+ return;
+ }
+
+ bool submittedMore = false;
+
+ while (!mAccessUnits.empty()
+ && !mDecoderInputBuffersAvailable.empty()) {
+ size_t index = *mDecoderInputBuffersAvailable.begin();
+
+ mDecoderInputBuffersAvailable.erase(
+ mDecoderInputBuffersAvailable.begin());
+
+ sp<ABuffer> srcBuffer = *mAccessUnits.begin();
+ mAccessUnits.erase(mAccessUnits.begin());
+
+ const sp<ABuffer> &dstBuffer =
+ mDecoderInputBuffers.itemAt(index);
+
+ memcpy(dstBuffer->data(), srcBuffer->data(), srcBuffer->size());
+
+ int64_t timeUs;
+ CHECK(srcBuffer->meta()->findInt64("timeUs", &timeUs));
+
+ status_t err = mDecoder->queueInputBuffer(
+ index,
+ 0 /* offset */,
+ srcBuffer->size(),
+ timeUs,
+ 0 /* flags */);
+ CHECK_EQ(err, (status_t)OK);
+
+ submittedMore = true;
+ }
+
+ if (submittedMore) {
+ scheduleDecoderNotification();
+ }
+}
+
+void DirectRenderer::DecoderContext::onMessageReceived(
+ const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatDecoderNotify:
+ {
+ onDecoderNotify();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void DirectRenderer::DecoderContext::onDecoderNotify() {
+ mDecoderNotificationPending = false;
+
+ for (;;) {
+ size_t index;
+ status_t err = mDecoder->dequeueInputBuffer(&index);
+
+ if (err == OK) {
+ mDecoderInputBuffersAvailable.push_back(index);
+ } else if (err == -EAGAIN) {
+ break;
+ } else {
+ TRESPASS();
+ }
+ }
+
+ queueDecoderInputBuffers();
+
+ for (;;) {
+ size_t index;
+ size_t offset;
+ size_t size;
+ int64_t timeUs;
+ uint32_t flags;
+ status_t err = mDecoder->dequeueOutputBuffer(
+ &index,
+ &offset,
+ &size,
+ &timeUs,
+ &flags);
+
+ if (err == OK) {
+ queueOutputBuffer(
+ index, timeUs, mDecoderOutputBuffers.itemAt(index));
+ } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) {
+ err = mDecoder->getOutputBuffers(
+ &mDecoderOutputBuffers);
+ CHECK_EQ(err, (status_t)OK);
+ } else if (err == INFO_FORMAT_CHANGED) {
+ // We don't care.
+ } else if (err == -EAGAIN) {
+ break;
+ } else {
+ TRESPASS();
+ }
+ }
+
+ scheduleDecoderNotification();
+}
+
+void DirectRenderer::DecoderContext::scheduleDecoderNotification() {
+ if (mDecoderNotificationPending) {
+ return;
+ }
+
+ sp<AMessage> notify =
+ new AMessage(kWhatDecoderNotify, id());
+
+ mDecoder->requestActivityNotification(notify);
+ mDecoderNotificationPending = true;
+}
+
+void DirectRenderer::DecoderContext::queueOutputBuffer(
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer) {
+ sp<AMessage> msg = mNotify->dup();
+ msg->setInt32("what", kWhatOutputBufferReady);
+ msg->setSize("index", index);
+ msg->setInt64("timeUs", timeUs);
+ msg->setBuffer("buffer", buffer);
+ msg->post();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+DirectRenderer::AudioRenderer::AudioRenderer(
+ const sp<DecoderContext> &decoderContext)
+ : mDecoderContext(decoderContext),
+ mPushPending(false),
+ mNumFramesWritten(0) {
+ mAudioTrack = new AudioTrack(
+ AUDIO_STREAM_DEFAULT,
+ 48000.0f,
+ AUDIO_FORMAT_PCM,
+ AUDIO_CHANNEL_OUT_STEREO,
+ (int)0 /* frameCount */);
+
+ CHECK_EQ((status_t)OK, mAudioTrack->initCheck());
+
+ mAudioTrack->start();
+}
+
+DirectRenderer::AudioRenderer::~AudioRenderer() {
+}
+
+void DirectRenderer::AudioRenderer::queueInputBuffer(
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer) {
+ BufferInfo info;
+ info.mIndex = index;
+ info.mTimeUs = timeUs;
+ info.mBuffer = buffer;
+
+ mInputBuffers.push_back(info);
+ schedulePushIfNecessary();
+}
+
+void DirectRenderer::AudioRenderer::onMessageReceived(
+ const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatPushAudio:
+ {
+ onPushAudio();
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void DirectRenderer::AudioRenderer::schedulePushIfNecessary() {
+ if (mPushPending || mInputBuffers.empty()) {
+ return;
+ }
+
+ mPushPending = true;
+
+ uint32_t numFramesPlayed;
+ CHECK_EQ(mAudioTrack->getPosition(&numFramesPlayed),
+ (status_t)OK);
+
+ uint32_t numFramesPendingPlayout = mNumFramesWritten - numFramesPlayed;
+
+ // This is how long the audio sink will have data to
+ // play back.
+ const float msecsPerFrame = 1000.0f / mAudioTrack->getSampleRate();
+
+ int64_t delayUs =
+ msecsPerFrame * numFramesPendingPlayout * 1000ll;
+
+ // Let's give it more data after about half that time
+ // has elapsed.
+ (new AMessage(kWhatPushAudio, id()))->post(delayUs / 2);
+}
+
+void DirectRenderer::AudioRenderer::onPushAudio() {
+ mPushPending = false;
+
+ while (!mInputBuffers.empty()) {
+ const BufferInfo &info = *mInputBuffers.begin();
+
+ ssize_t n = writeNonBlocking(
+ info.mBuffer->data(), info.mBuffer->size());
+
+ if (n < (ssize_t)info.mBuffer->size()) {
+ CHECK_GE(n, 0);
+
+ info.mBuffer->setRange(
+ info.mBuffer->offset() + n, info.mBuffer->size() - n);
+ break;
+ }
+
+ mDecoderContext->releaseOutputBuffer(info.mIndex);
+
+ mInputBuffers.erase(mInputBuffers.begin());
+ }
+
+ schedulePushIfNecessary();
+}
+
+ssize_t DirectRenderer::AudioRenderer::writeNonBlocking(
+ const uint8_t *data, size_t size) {
+ uint32_t numFramesPlayed;
+ status_t err = mAudioTrack->getPosition(&numFramesPlayed);
+ if (err != OK) {
+ return err;
+ }
+
+ ssize_t numFramesAvailableToWrite =
+ mAudioTrack->frameCount() - (mNumFramesWritten - numFramesPlayed);
+
+ size_t numBytesAvailableToWrite =
+ numFramesAvailableToWrite * mAudioTrack->frameSize();
+
+ if (size > numBytesAvailableToWrite) {
+ size = numBytesAvailableToWrite;
+ }
+
+ CHECK_EQ(mAudioTrack->write(data, size), (ssize_t)size);
+
+ size_t numFramesWritten = size / mAudioTrack->frameSize();
+ mNumFramesWritten += numFramesWritten;
+
+ return size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+DirectRenderer::DirectRenderer(
+ const sp<IGraphicBufferProducer> &bufferProducer)
+ : mSurfaceTex(bufferProducer),
+ mVideoRenderPending(false),
+ mNumFramesLate(0),
+ mNumFrames(0) {
+}
+
+DirectRenderer::~DirectRenderer() {
+}
+
+void DirectRenderer::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatDecoderNotify:
+ {
+ onDecoderNotify(msg);
+ break;
+ }
+
+ case kWhatRenderVideo:
+ {
+ onRenderVideo();
+ break;
+ }
+
+ case kWhatQueueAccessUnit:
+ onQueueAccessUnit(msg);
+ break;
+
+ case kWhatSetFormat:
+ onSetFormat(msg);
+ break;
+
+ default:
+ TRESPASS();
+ }
+}
+
+void DirectRenderer::setFormat(size_t trackIndex, const sp<AMessage> &format) {
+ sp<AMessage> msg = new AMessage(kWhatSetFormat, id());
+ msg->setSize("trackIndex", trackIndex);
+ msg->setMessage("format", format);
+ msg->post();
+}
+
+void DirectRenderer::onSetFormat(const sp<AMessage> &msg) {
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ sp<AMessage> format;
+ CHECK(msg->findMessage("format", &format));
+
+ internalSetFormat(trackIndex, format);
+}
+
+void DirectRenderer::internalSetFormat(
+ size_t trackIndex, const sp<AMessage> &format) {
+ CHECK_LT(trackIndex, 2u);
+
+ CHECK(mDecoderContext[trackIndex] == NULL);
+
+ sp<AMessage> notify = new AMessage(kWhatDecoderNotify, id());
+ notify->setSize("trackIndex", trackIndex);
+
+ mDecoderContext[trackIndex] = new DecoderContext(notify);
+ looper()->registerHandler(mDecoderContext[trackIndex]);
+
+ CHECK_EQ((status_t)OK,
+ mDecoderContext[trackIndex]->init(
+ format, trackIndex == 0 ? mSurfaceTex : NULL));
+
+ if (trackIndex == 1) {
+ // Audio
+ mAudioRenderer = new AudioRenderer(mDecoderContext[1]);
+ looper()->registerHandler(mAudioRenderer);
+ }
+}
+
+void DirectRenderer::queueAccessUnit(
+ size_t trackIndex, const sp<ABuffer> &accessUnit) {
+ sp<AMessage> msg = new AMessage(kWhatQueueAccessUnit, id());
+ msg->setSize("trackIndex", trackIndex);
+ msg->setBuffer("accessUnit", accessUnit);
+ msg->post();
+}
+
+void DirectRenderer::onQueueAccessUnit(const sp<AMessage> &msg) {
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ CHECK_LT(trackIndex, 2u);
+ CHECK(mDecoderContext[trackIndex] != NULL);
+
+ mDecoderContext[trackIndex]->queueInputBuffer(accessUnit);
+}
+
+void DirectRenderer::onDecoderNotify(const sp<AMessage> &msg) {
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case DecoderContext::kWhatOutputBufferReady:
+ {
+ size_t index;
+ CHECK(msg->findSize("index", &index));
+
+ int64_t timeUs;
+ CHECK(msg->findInt64("timeUs", &timeUs));
+
+ sp<ABuffer> buffer;
+ CHECK(msg->findBuffer("buffer", &buffer));
+
+ queueOutputBuffer(trackIndex, index, timeUs, buffer);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void DirectRenderer::queueOutputBuffer(
+ size_t trackIndex,
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer) {
+ if (trackIndex == 1) {
+ // Audio
+ mAudioRenderer->queueInputBuffer(index, timeUs, buffer);
+ return;
+ }
+
+ OutputInfo info;
+ info.mIndex = index;
+ info.mTimeUs = timeUs;
+ info.mBuffer = buffer;
+ mVideoOutputBuffers.push_back(info);
+
+ scheduleVideoRenderIfNecessary();
+}
+
+void DirectRenderer::scheduleVideoRenderIfNecessary() {
+ if (mVideoRenderPending || mVideoOutputBuffers.empty()) {
+ return;
+ }
+
+ mVideoRenderPending = true;
+
+ int64_t timeUs = (*mVideoOutputBuffers.begin()).mTimeUs;
+ int64_t nowUs = ALooper::GetNowUs();
+
+ int64_t delayUs = timeUs - nowUs;
+
+ (new AMessage(kWhatRenderVideo, id()))->post(delayUs);
+}
+
+void DirectRenderer::onRenderVideo() {
+ mVideoRenderPending = false;
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ while (!mVideoOutputBuffers.empty()) {
+ const OutputInfo &info = *mVideoOutputBuffers.begin();
+
+ if (info.mTimeUs > nowUs) {
+ break;
+ }
+
+ if (info.mTimeUs + 15000ll < nowUs) {
+ ++mNumFramesLate;
+ }
+ ++mNumFrames;
+
+ status_t err =
+ mDecoderContext[0]->renderOutputBufferAndRelease(info.mIndex);
+ CHECK_EQ(err, (status_t)OK);
+
+ mVideoOutputBuffers.erase(mVideoOutputBuffers.begin());
+ }
+
+ scheduleVideoRenderIfNecessary();
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/sink/DirectRenderer.h b/media/libstagefright/wifi-display/sink/DirectRenderer.h
new file mode 100644
index 0000000..07c2170
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/DirectRenderer.h
@@ -0,0 +1,87 @@
+/*
+ * 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 DIRECT_RENDERER_H_
+
+#define DIRECT_RENDERER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+struct IGraphicBufferProducer;
+
+// Renders audio and video data queued by calls to "queueAccessUnit".
+struct DirectRenderer : public AHandler {
+ DirectRenderer(const sp<IGraphicBufferProducer> &bufferProducer);
+
+ void setFormat(size_t trackIndex, const sp<AMessage> &format);
+ void queueAccessUnit(size_t trackIndex, const sp<ABuffer> &accessUnit);
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~DirectRenderer();
+
+private:
+ struct DecoderContext;
+ struct AudioRenderer;
+
+ enum {
+ kWhatDecoderNotify,
+ kWhatRenderVideo,
+ kWhatQueueAccessUnit,
+ kWhatSetFormat,
+ };
+
+ struct OutputInfo {
+ size_t mIndex;
+ int64_t mTimeUs;
+ sp<ABuffer> mBuffer;
+ };
+
+ sp<IGraphicBufferProducer> mSurfaceTex;
+
+ sp<DecoderContext> mDecoderContext[2];
+ List<OutputInfo> mVideoOutputBuffers;
+
+ bool mVideoRenderPending;
+
+ sp<AudioRenderer> mAudioRenderer;
+
+ int32_t mNumFramesLate;
+ int32_t mNumFrames;
+
+ void onDecoderNotify(const sp<AMessage> &msg);
+
+ void queueOutputBuffer(
+ size_t trackIndex,
+ size_t index, int64_t timeUs, const sp<ABuffer> &buffer);
+
+ void scheduleVideoRenderIfNecessary();
+ void onRenderVideo();
+
+ void onSetFormat(const sp<AMessage> &msg);
+ void onQueueAccessUnit(const sp<AMessage> &msg);
+
+ void internalSetFormat(size_t trackIndex, const sp<AMessage> &format);
+
+ DISALLOW_EVIL_CONSTRUCTORS(DirectRenderer);
+};
+
+} // namespace android
+
+#endif // DIRECT_RENDERER_H_
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
new file mode 100644
index 0000000..bc88f1e
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
@@ -0,0 +1,917 @@
+/*
+ * 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 "WifiDisplaySink"
+#include <utils/Log.h>
+
+#include "WifiDisplaySink.h"
+
+#include "DirectRenderer.h"
+#include "MediaReceiver.h"
+#include "TimeSyncer.h"
+
+#include <cutils/properties.h>
+#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>
+
+namespace android {
+
+// static
+const AString WifiDisplaySink::sUserAgent = MakeUserAgent();
+
+WifiDisplaySink::WifiDisplaySink(
+ uint32_t flags,
+ const sp<ANetworkSession> &netSession,
+ const sp<IGraphicBufferProducer> &bufferProducer,
+ const sp<AMessage> &notify)
+ : mState(UNDEFINED),
+ mFlags(flags),
+ mNetSession(netSession),
+ mSurfaceTex(bufferProducer),
+ mNotify(notify),
+ mUsingTCPTransport(false),
+ mUsingTCPInterleaving(false),
+ mSessionID(0),
+ mNextCSeq(1),
+ mIDRFrameRequestPending(false),
+ mTimeOffsetUs(0ll),
+ mTimeOffsetValid(false),
+ mSetupDeferred(false),
+ mLatencyCount(0),
+ mLatencySumUs(0ll),
+ mLatencyMaxUs(0ll),
+ mMaxDelayMs(-1ll) {
+ // We support any and all resolutions, but prefer 720p30
+ mSinkSupportedVideoFormats.setNativeResolution(
+ VideoFormats::RESOLUTION_CEA, 5); // 1280 x 720 p30
+
+ mSinkSupportedVideoFormats.enableAll();
+}
+
+WifiDisplaySink::~WifiDisplaySink() {
+}
+
+void WifiDisplaySink::start(const char *sourceHost, int32_t sourcePort) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setString("sourceHost", sourceHost);
+ msg->setInt32("sourcePort", sourcePort);
+ msg->post();
+}
+
+void WifiDisplaySink::start(const char *uri) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setString("setupURI", uri);
+ msg->post();
+}
+
+// static
+bool WifiDisplaySink::ParseURL(
+ const char *url, AString *host, int32_t *port, AString *path,
+ AString *user, AString *pass) {
+ host->clear();
+ *port = 0;
+ path->clear();
+ user->clear();
+ pass->clear();
+
+ if (strncasecmp("rtsp://", url, 7)) {
+ return false;
+ }
+
+ const char *slashPos = strchr(&url[7], '/');
+
+ if (slashPos == NULL) {
+ host->setTo(&url[7]);
+ path->setTo("/");
+ } else {
+ host->setTo(&url[7], slashPos - &url[7]);
+ path->setTo(slashPos);
+ }
+
+ ssize_t atPos = host->find("@");
+
+ if (atPos >= 0) {
+ // Split of user:pass@ from hostname.
+
+ AString userPass(*host, 0, atPos);
+ host->erase(0, atPos + 1);
+
+ ssize_t colonPos = userPass.find(":");
+
+ if (colonPos < 0) {
+ *user = userPass;
+ } else {
+ user->setTo(userPass, 0, colonPos);
+ pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1);
+ }
+ }
+
+ const char *colonPos = strchr(host->c_str(), ':');
+
+ if (colonPos != NULL) {
+ char *end;
+ unsigned long x = strtoul(colonPos + 1, &end, 10);
+
+ if (end == colonPos + 1 || *end != '\0' || x >= 65536) {
+ return false;
+ }
+
+ *port = x;
+
+ size_t colonOffset = colonPos - host->c_str();
+ size_t trailing = host->size() - colonOffset;
+ host->erase(colonOffset, trailing);
+ } else {
+ *port = 554;
+ }
+
+ return true;
+}
+
+void WifiDisplaySink::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ sleep(2); // XXX
+
+ int32_t sourcePort;
+ CHECK(msg->findString("sourceHost", &mRTSPHost));
+ CHECK(msg->findInt32("sourcePort", &sourcePort));
+
+ sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
+
+ status_t err = mNetSession->createRTSPClient(
+ mRTSPHost.c_str(), sourcePort, notify, &mSessionID);
+ CHECK_EQ(err, (status_t)OK);
+
+ mState = CONNECTING;
+ break;
+ }
+
+ case kWhatRTSPNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred in session %d (%d, '%s/%s').",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ if (sessionID == mSessionID) {
+ ALOGI("Lost control connection.");
+
+ // The control connection is dead now.
+ mNetSession->destroySession(mSessionID);
+ mSessionID = 0;
+
+ if (mNotify == NULL) {
+ looper()->stop();
+ } else {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatDisconnected);
+ notify->post();
+ }
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatConnected:
+ {
+ ALOGI("We're now connected.");
+ mState = CONNECTED;
+
+ if (mFlags & FLAG_SPECIAL_MODE) {
+ sp<AMessage> notify = new AMessage(
+ kWhatTimeSyncerNotify, id());
+
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ mTimeSyncer->startClient(mRTSPHost.c_str(), 8123);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatData:
+ {
+ onReceiveClientData(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatStop:
+ {
+ looper()->stop();
+ break;
+ }
+
+ case kWhatMediaReceiverNotify:
+ {
+ onMediaReceiverNotify(msg);
+ break;
+ }
+
+ case kWhatTimeSyncerNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ if (what == TimeSyncer::kWhatTimeOffset) {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+
+ if (mSetupDeferred) {
+ CHECK_EQ((status_t)OK,
+ sendSetup(
+ mSessionID,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0"));
+
+ mSetupDeferred = false;
+ }
+ }
+ break;
+ }
+
+ case kWhatReportLateness:
+ {
+ if (mLatencyCount > 0) {
+ int64_t avgLatencyUs = mLatencySumUs / mLatencyCount;
+
+ ALOGV("avg. latency = %lld ms (max %lld ms)",
+ avgLatencyUs / 1000ll,
+ mLatencyMaxUs / 1000ll);
+
+ sp<AMessage> params = new AMessage;
+ params->setInt64("avgLatencyUs", avgLatencyUs);
+ params->setInt64("maxLatencyUs", mLatencyMaxUs);
+ mMediaReceiver->informSender(0 /* trackIndex */, params);
+ }
+
+ mLatencyCount = 0;
+ mLatencySumUs = 0ll;
+ mLatencyMaxUs = 0ll;
+
+ msg->post(kReportLatenessEveryUs);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySink::dumpDelay(size_t trackIndex, int64_t timeUs) {
+ int64_t delayMs = (ALooper::GetNowUs() - timeUs) / 1000ll;
+
+ if (delayMs > mMaxDelayMs) {
+ mMaxDelayMs = delayMs;
+ }
+
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ ALOGI("[%lld]: (%4lld ms / %4lld ms) %s",
+ timeUs / 1000,
+ delayMs,
+ mMaxDelayMs,
+ kPattern + kPatternSize - n);
+}
+
+void WifiDisplaySink::onMediaReceiverNotify(const sp<AMessage> &msg) {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case MediaReceiver::kWhatInitDone:
+ {
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGI("MediaReceiver initialization completed w/ err %d", err);
+ break;
+ }
+
+ case MediaReceiver::kWhatError:
+ {
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGE("MediaReceiver signaled error %d", err);
+ break;
+ }
+
+ case MediaReceiver::kWhatAccessUnit:
+ {
+ if (mRenderer == NULL) {
+ mRenderer = new DirectRenderer(mSurfaceTex);
+ looper()->registerHandler(mRenderer);
+ }
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ if (!mTimeOffsetValid && !(mFlags & FLAG_SPECIAL_MODE)) {
+ mTimeOffsetUs = timeUs - ALooper::GetNowUs();
+ mTimeOffsetValid = true;
+ }
+
+ CHECK(mTimeOffsetValid);
+
+ // We are the timesync _client_,
+ // client time = server time - time offset.
+ timeUs -= mTimeOffsetUs;
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayUs = nowUs - timeUs;
+
+ mLatencySumUs += delayUs;
+ if (mLatencyCount == 0 || delayUs > mLatencyMaxUs) {
+ mLatencyMaxUs = delayUs;
+ }
+ ++mLatencyCount;
+
+ // dumpDelay(trackIndex, timeUs);
+
+ timeUs += 220000ll; // Assume 220 ms of latency
+ accessUnit->meta()->setInt64("timeUs", timeUs);
+
+ sp<AMessage> format;
+ if (msg->findMessage("format", &format)) {
+ mRenderer->setFormat(trackIndex, format);
+ }
+
+ mRenderer->queueAccessUnit(trackIndex, accessUnit);
+ break;
+ }
+
+ case MediaReceiver::kWhatPacketLost:
+ {
+#if 0
+ if (!mIDRFrameRequestPending) {
+ ALOGI("requesting IDR frame");
+
+ sendIDRFrameRequest(mSessionID);
+ }
+#endif
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySink::registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) {
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+ mResponseHandlers.add(id, func);
+}
+
+status_t WifiDisplaySink::sendM2(int32_t sessionID) {
+ AString request = "OPTIONS * RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append(
+ "Require: org.wfa.wfd1.0\r\n"
+ "\r\n");
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceiveM2Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveM2Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveSetupResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (!msg->findString("session", &mPlaybackSessionID)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (!ParsedMessage::GetInt32Attribute(
+ mPlaybackSessionID.c_str(),
+ "timeout",
+ &mPlaybackSessionTimeoutSecs)) {
+ mPlaybackSessionTimeoutSecs = -1;
+ }
+
+ ssize_t colonPos = mPlaybackSessionID.find(";");
+ if (colonPos >= 0) {
+ // Strip any options from the returned session id.
+ mPlaybackSessionID.erase(
+ colonPos, mPlaybackSessionID.size() - colonPos);
+ }
+
+ status_t err = configureTransport(msg);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mState = PAUSED;
+
+ return sendPlay(
+ sessionID,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0");
+}
+
+status_t WifiDisplaySink::configureTransport(const sp<ParsedMessage> &msg) {
+ if (mUsingTCPTransport && !(mFlags & FLAG_SPECIAL_MODE)) {
+ // In "special" mode we still use a UDP RTCP back-channel that
+ // needs connecting.
+ return OK;
+ }
+
+ AString transport;
+ if (!msg->findString("transport", &transport)) {
+ ALOGE("Missing 'transport' field in SETUP response.");
+ return ERROR_MALFORMED;
+ }
+
+ AString sourceHost;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "source", &sourceHost)) {
+ sourceHost = mRTSPHost;
+ }
+
+ AString serverPortStr;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "server_port", &serverPortStr)) {
+ ALOGE("Missing 'server_port' in Transport field.");
+ return ERROR_MALFORMED;
+ }
+
+ int rtpPort, rtcpPort;
+ if (sscanf(serverPortStr.c_str(), "%d-%d", &rtpPort, &rtcpPort) != 2
+ || rtpPort <= 0 || rtpPort > 65535
+ || rtcpPort <=0 || rtcpPort > 65535
+ || rtcpPort != rtpPort + 1) {
+ ALOGE("Invalid server_port description '%s'.",
+ serverPortStr.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ if (rtpPort & 1) {
+ ALOGW("Server picked an odd numbered RTP port.");
+ }
+
+ return mMediaReceiver->connectTrack(
+ 0 /* trackIndex */, sourceHost.c_str(), rtpPort, rtcpPort);
+}
+
+status_t WifiDisplaySink::onReceivePlayResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ mState = PLAYING;
+
+ (new AMessage(kWhatReportLateness, id()))->post(kReportLatenessEveryUs);
+
+ return OK;
+}
+
+status_t WifiDisplaySink::onReceiveIDRFrameRequestResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ CHECK(mIDRFrameRequestPending);
+ mIDRFrameRequestPending = false;
+
+ return OK;
+}
+
+void WifiDisplaySink::onReceiveClientData(const sp<AMessage> &msg) {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("data", &obj));
+
+ sp<ParsedMessage> data =
+ static_cast<ParsedMessage *>(obj.get());
+
+ ALOGV("session %d received '%s'",
+ sessionID, data->debugString().c_str());
+
+ AString method;
+ AString uri;
+ data->getRequestField(0, &method);
+
+ int32_t cseq;
+ if (!data->findInt32("cseq", &cseq)) {
+ sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */);
+ return;
+ }
+
+ if (method.startsWith("RTSP/")) {
+ // This is a response.
+
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+
+ ssize_t index = mResponseHandlers.indexOfKey(id);
+
+ if (index < 0) {
+ ALOGW("Received unsolicited server response, cseq %d", cseq);
+ return;
+ }
+
+ HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index);
+ mResponseHandlers.removeItemsAt(index);
+
+ status_t err = (this->*func)(sessionID, data);
+ CHECK_EQ(err, (status_t)OK);
+ } else {
+ AString version;
+ data->getRequestField(2, &version);
+ if (!(version == AString("RTSP/1.0"))) {
+ sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
+ return;
+ }
+
+ if (method == "OPTIONS") {
+ onOptionsRequest(sessionID, cseq, data);
+ } else if (method == "GET_PARAMETER") {
+ onGetParameterRequest(sessionID, cseq, data);
+ } else if (method == "SET_PARAMETER") {
+ onSetParameterRequest(sessionID, cseq, data);
+ } else {
+ sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
+ }
+ }
+}
+
+void WifiDisplaySink::onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("Public: org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER\r\n");
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+
+ err = sendM2(sessionID);
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySink::onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ AString body;
+
+ if (mState == CONNECTED) {
+ mUsingTCPTransport = false;
+ mUsingTCPInterleaving = false;
+
+ char val[PROPERTY_VALUE_MAX];
+ if (property_get("media.wfd-sink.tcp-mode", val, NULL)) {
+ if (!strcasecmp("true", val) || !strcmp("1", val)) {
+ ALOGI("Using TCP unicast transport.");
+ mUsingTCPTransport = true;
+ mUsingTCPInterleaving = false;
+ } else if (!strcasecmp("interleaved", val)) {
+ ALOGI("Using TCP interleaved transport.");
+ mUsingTCPTransport = true;
+ mUsingTCPInterleaving = true;
+ }
+ } else if (mFlags & FLAG_SPECIAL_MODE) {
+ mUsingTCPTransport = true;
+ }
+
+ body = "wfd_video_formats: ";
+ body.append(mSinkSupportedVideoFormats.getFormatSpec());
+
+ body.append(
+ "\r\nwfd_audio_codecs: AAC 0000000F 00\r\n"
+ "wfd_client_rtp_ports: RTP/AVP/");
+
+ if (mUsingTCPTransport) {
+ body.append("TCP;");
+ if (mUsingTCPInterleaving) {
+ body.append("interleaved");
+ } else {
+ body.append("unicast 19000 0");
+ }
+ } else {
+ body.append("UDP;unicast 19000 0");
+ }
+
+ body.append(" mode=play\r\n");
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("Content-Type: text/parameters\r\n");
+ response.append(StringPrintf("Content-Length: %d\r\n", body.size()));
+ response.append("\r\n");
+ response.append(body);
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+status_t WifiDisplaySink::sendSetup(int32_t sessionID, const char *uri) {
+ sp<AMessage> notify = new AMessage(kWhatMediaReceiverNotify, id());
+
+ mMediaReceiverLooper = new ALooper;
+ mMediaReceiverLooper->setName("media_receiver");
+
+ mMediaReceiverLooper->start(
+ false /* runOnCallingThread */,
+ false /* canCallJava */,
+ PRIORITY_AUDIO);
+
+ mMediaReceiver = new MediaReceiver(mNetSession, notify);
+ mMediaReceiverLooper->registerHandler(mMediaReceiver);
+
+ RTPReceiver::TransportMode rtpMode = RTPReceiver::TRANSPORT_UDP;
+ if (mUsingTCPTransport) {
+ if (mUsingTCPInterleaving) {
+ rtpMode = RTPReceiver::TRANSPORT_TCP_INTERLEAVED;
+ } else {
+ rtpMode = RTPReceiver::TRANSPORT_TCP;
+ }
+ }
+
+ int32_t localRTPPort;
+ status_t err = mMediaReceiver->addTrack(
+ rtpMode, RTPReceiver::TRANSPORT_UDP /* rtcpMode */, &localRTPPort);
+
+ if (err == OK) {
+ err = mMediaReceiver->initAsync(MediaReceiver::MODE_TRANSPORT_STREAM);
+ }
+
+ if (err != OK) {
+ mMediaReceiverLooper->unregisterHandler(mMediaReceiver->id());
+ mMediaReceiver.clear();
+
+ mMediaReceiverLooper->stop();
+ mMediaReceiverLooper.clear();
+
+ return err;
+ }
+
+ AString request = StringPrintf("SETUP %s RTSP/1.0\r\n", uri);
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ if (rtpMode == RTPReceiver::TRANSPORT_TCP_INTERLEAVED) {
+ request.append("Transport: RTP/AVP/TCP;interleaved=0-1\r\n");
+ } else if (rtpMode == RTPReceiver::TRANSPORT_TCP) {
+ if (mFlags & FLAG_SPECIAL_MODE) {
+ // This isn't quite true, since the RTP connection is through TCP
+ // and the RTCP connection through UDP...
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/TCP;unicast;client_port=%d-%d\r\n",
+ localRTPPort, localRTPPort + 1));
+ } else {
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/TCP;unicast;client_port=%d\r\n",
+ localRTPPort));
+ }
+ } else {
+ request.append(
+ StringPrintf(
+ "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
+ localRTPPort,
+ localRTPPort + 1));
+ }
+
+ request.append("\r\n");
+
+ ALOGV("request = '%s'", request.c_str());
+
+ err = mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceiveSetupResponse);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::sendPlay(int32_t sessionID, const char *uri) {
+ AString request = StringPrintf("PLAY %s RTSP/1.0\r\n", uri);
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
+ request.append("\r\n");
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySink::onReceivePlayResponse);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySink::sendIDRFrameRequest(int32_t sessionID) {
+ CHECK(!mIDRFrameRequestPending);
+
+ AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+
+ AppendCommonResponse(&request, mNextCSeq);
+
+ AString content = "wfd_idr_request\r\n";
+
+ request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
+ request.append(StringPrintf("Content-Length: %d\r\n", content.size()));
+ request.append("\r\n");
+ request.append(content);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID,
+ mNextCSeq,
+ &WifiDisplaySink::onReceiveIDRFrameRequestResponse);
+
+ ++mNextCSeq;
+
+ mIDRFrameRequestPending = true;
+
+ return OK;
+}
+
+void WifiDisplaySink::onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ const char *content = data->getContent();
+
+ if (strstr(content, "wfd_trigger_method: SETUP\r\n") != NULL) {
+ if ((mFlags & FLAG_SPECIAL_MODE) && !mTimeOffsetValid) {
+ mSetupDeferred = true;
+ } else {
+ status_t err =
+ sendSetup(
+ sessionID,
+ "rtsp://x.x.x.x:x/wfd1.0/streamid=0");
+
+ CHECK_EQ(err, (status_t)OK);
+ }
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+void WifiDisplaySink::sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq) {
+ AString response;
+ response.append("RTSP/1.0 ");
+ response.append(errorDetail);
+ response.append("\r\n");
+
+ AppendCommonResponse(&response, cseq);
+
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ CHECK_EQ(err, (status_t)OK);
+}
+
+// static
+void WifiDisplaySink::AppendCommonResponse(AString *response, int32_t cseq) {
+ time_t now = time(NULL);
+ struct tm *now2 = gmtime(&now);
+ char buf[128];
+ strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", now2);
+
+ response->append("Date: ");
+ response->append(buf);
+ response->append("\r\n");
+
+ response->append(StringPrintf("User-Agent: %s\r\n", sUserAgent.c_str()));
+
+ if (cseq >= 0) {
+ response->append(StringPrintf("CSeq: %d\r\n", cseq));
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.h b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
new file mode 100644
index 0000000..dc1fc32
--- /dev/null
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
@@ -0,0 +1,195 @@
+/*
+ * 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 WIFI_DISPLAY_SINK_H_
+
+#define WIFI_DISPLAY_SINK_H_
+
+#include "VideoFormats.h"
+
+#include <gui/Surface.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
+
+namespace android {
+
+struct AMessage;
+struct DirectRenderer;
+struct MediaReceiver;
+struct ParsedMessage;
+struct TimeSyncer;
+
+// Represents the RTSP client acting as a wifi display sink.
+// Connects to a wifi display source and renders the incoming
+// transport stream using a MediaPlayer instance.
+struct WifiDisplaySink : public AHandler {
+ enum {
+ kWhatDisconnected,
+ };
+
+ enum Flags {
+ FLAG_SPECIAL_MODE = 1,
+ };
+
+ // If no notification message is specified (notify == NULL)
+ // the sink will stop its looper() once the session ends,
+ // otherwise it will post an appropriate notification but leave
+ // the looper() running.
+ WifiDisplaySink(
+ uint32_t flags,
+ const sp<ANetworkSession> &netSession,
+ const sp<IGraphicBufferProducer> &bufferProducer = NULL,
+ const sp<AMessage> &notify = NULL);
+
+ void start(const char *sourceHost, int32_t sourcePort);
+ void start(const char *uri);
+
+protected:
+ virtual ~WifiDisplaySink();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum State {
+ UNDEFINED,
+ CONNECTING,
+ CONNECTED,
+ PAUSED,
+ PLAYING,
+ };
+
+ enum {
+ kWhatStart,
+ kWhatRTSPNotify,
+ kWhatStop,
+ kWhatMediaReceiverNotify,
+ kWhatTimeSyncerNotify,
+ kWhatReportLateness,
+ };
+
+ struct ResponseID {
+ int32_t mSessionID;
+ int32_t mCSeq;
+
+ bool operator<(const ResponseID &other) const {
+ return mSessionID < other.mSessionID
+ || (mSessionID == other.mSessionID
+ && mCSeq < other.mCSeq);
+ }
+ };
+
+ typedef status_t (WifiDisplaySink::*HandleRTSPResponseFunc)(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ static const int64_t kReportLatenessEveryUs = 1000000ll;
+
+ static const AString sUserAgent;
+
+ State mState;
+ uint32_t mFlags;
+ VideoFormats mSinkSupportedVideoFormats;
+ sp<ANetworkSession> mNetSession;
+ sp<IGraphicBufferProducer> mSurfaceTex;
+ sp<AMessage> mNotify;
+ sp<TimeSyncer> mTimeSyncer;
+ bool mUsingTCPTransport;
+ bool mUsingTCPInterleaving;
+ AString mRTSPHost;
+ int32_t mSessionID;
+
+ int32_t mNextCSeq;
+
+ KeyedVector<ResponseID, HandleRTSPResponseFunc> mResponseHandlers;
+
+ sp<ALooper> mMediaReceiverLooper;
+ sp<MediaReceiver> mMediaReceiver;
+ sp<DirectRenderer> mRenderer;
+
+ AString mPlaybackSessionID;
+ int32_t mPlaybackSessionTimeoutSecs;
+
+ bool mIDRFrameRequestPending;
+
+ int64_t mTimeOffsetUs;
+ bool mTimeOffsetValid;
+
+ bool mSetupDeferred;
+
+ size_t mLatencyCount;
+ int64_t mLatencySumUs;
+ int64_t mLatencyMaxUs;
+
+ int64_t mMaxDelayMs;
+
+ status_t sendM2(int32_t sessionID);
+ status_t sendSetup(int32_t sessionID, const char *uri);
+ status_t sendPlay(int32_t sessionID, const char *uri);
+ status_t sendIDRFrameRequest(int32_t sessionID);
+
+ status_t onReceiveM2Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveSetupResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t configureTransport(const sp<ParsedMessage> &msg);
+
+ status_t onReceivePlayResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveIDRFrameRequestResponse(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ void registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
+
+ void onReceiveClientData(const sp<AMessage> &msg);
+
+ void onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void onMediaReceiverNotify(const sp<AMessage> &msg);
+
+ void sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq);
+
+ static void AppendCommonResponse(AString *response, int32_t cseq);
+
+ bool ParseURL(
+ const char *url, AString *host, int32_t *port, AString *path,
+ AString *user, AString *pass);
+
+ void dumpDelay(size_t trackIndex, int64_t timeUs);
+
+ DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySink);
+};
+
+} // namespace android
+
+#endif // WIFI_DISPLAY_SINK_H_
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..d72349d 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -21,8 +21,8 @@
#include "WifiDisplaySource.h"
#include "PlaybackSession.h"
#include "Parameters.h"
-#include "ParsedMessage.h"
#include "rtp/RTPSender.h"
+#include "TimeSyncer.h"
#include <binder/IServiceManager.h>
#include <gui/IGraphicBufferProducer.h>
@@ -32,6 +32,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 +74,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,6 +171,14 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
} else {
err = -EINVAL;
}
+ }
+
+ if (err == OK) {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ mTimeSyncer->startServer(8123);
mState = AWAITING_CLIENT_CONNECTION;
}
@@ -401,7 +416,8 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
0, // height,
mUsingHDCP
? IRemoteDisplayClient::kDisplayFlagSecure
- : 0);
+ : 0,
+ 0);
} else {
size_t width, height;
@@ -420,7 +436,8 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
height,
mUsingHDCP
? IRemoteDisplayClient::kDisplayFlagSecure
- : 0);
+ : 0,
+ playbackSessionID);
}
}
@@ -539,6 +556,11 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
break;
}
+ case kWhatTimeSyncerNotify:
+ {
+ break;
+ }
+
default:
TRESPASS();
}
@@ -617,6 +639,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 +754,8 @@ status_t WifiDisplaySource::sendM16(int32_t sessionID) {
++mNextCSeq;
+ scheduleKeepAlive(sessionID);
+
return OK;
}
@@ -845,7 +872,9 @@ status_t WifiDisplaySource::onReceiveM3Response(
mSupportedSinkVideoFormats,
mSupportedSourceVideoFormats,
&mChosenVideoResolutionType,
- &mChosenVideoResolutionIndex)) {
+ &mChosenVideoResolutionIndex,
+ &mChosenVideoProfile,
+ &mChosenVideoLevel)) {
ALOGE("Sink and source share no commonly supported video "
"formats.");
@@ -864,6 +893,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 +1026,6 @@ status_t WifiDisplaySource::onReceiveM16Response(
if (mClientInfo.mPlaybackSession != NULL) {
mClientInfo.mPlaybackSession->updateLiveness();
-
- scheduleKeepAlive(sessionID);
}
return OK;
@@ -1257,7 +1287,9 @@ status_t WifiDisplaySource::onSetupRequest(
mUsingPCMAudio,
mSinkSupportsVideo,
mChosenVideoResolutionType,
- mChosenVideoResolutionIndex);
+ mChosenVideoResolutionIndex,
+ mChosenVideoProfile,
+ mChosenVideoLevel);
if (err != OK) {
looper()->unregisterHandler(playbackSession->id());
@@ -1340,7 +1372,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 +1401,7 @@ status_t WifiDisplaySource::onPlayRequest(
return err;
}
- if (mState == PAUSED_TO_PLAYING) {
+ if (mState == PAUSED_TO_PLAYING || mPlaybackSessionEstablished) {
mState = PLAYING;
return OK;
}
@@ -1401,7 +1435,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..4f11712 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>
@@ -30,6 +30,7 @@ namespace android {
struct IHDCP;
struct IRemoteDisplayClient;
struct ParsedMessage;
+struct TimeSyncer;
// Represents the RTSP server acting as a wifi display source.
// Manages incoming connections, sets up Playback sessions as necessary.
@@ -82,6 +83,7 @@ private:
kWhatHDCPNotify,
kWhatFinishStop2,
kWhatTeardownTriggerTimedOut,
+ kWhatTimeSyncerNotify,
};
struct ResponseID {
@@ -118,6 +120,7 @@ private:
sp<ANetworkSession> mNetSession;
sp<IRemoteDisplayClient> mClient;
AString mMediaPath;
+ sp<TimeSyncer> mTimeSyncer;
struct in_addr mInterfaceAddr;
int32_t mSessionID;
@@ -131,6 +134,8 @@ private:
VideoFormats::ResolutionType mChosenVideoResolutionType;
size_t mChosenVideoResolutionIndex;
+ VideoFormats::ProfileType mChosenVideoProfile;
+ VideoFormats::LevelType mChosenVideoLevel;
bool mSinkSupportsAudio;
diff --git a/media/libstagefright/wifi-display/udptest.cpp b/media/libstagefright/wifi-display/udptest.cpp
new file mode 100644
index 0000000..61eb9f9
--- /dev/null
+++ b/media/libstagefright/wifi-display/udptest.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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_NEBUG 0
+#define LOG_TAG "udptest"
+#include <utils/Log.h>
+
+#include "TimeSyncer.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
+
+namespace android {
+
+} // namespace android
+
+static void usage(const char *me) {
+ fprintf(stderr,
+ "usage: %s -c host[:port]\tconnect to test server\n"
+ " -l \tcreate a test server\n",
+ me);
+}
+
+int main(int argc, char **argv) {
+ using namespace android;
+
+ ProcessState::self()->startThreadPool();
+
+ int32_t localPort = -1;
+ int32_t connectToPort = -1;
+ AString connectToHost;
+
+ int res;
+ while ((res = getopt(argc, argv, "hc:l:")) >= 0) {
+ switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ connectToHost = optarg;
+ connectToPort = 49152;
+ } else {
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 1 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ }
+ break;
+ }
+
+ case 'l':
+ {
+ char *end;
+ localPort = strtol(optarg, &end, 10);
+
+ if (*end != '\0' || end == optarg
+ || localPort < 1 || localPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case '?':
+ case 'h':
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if (localPort < 0 && connectToPort < 0) {
+ fprintf(stderr,
+ "You need to select either client or server mode.\n");
+ exit(1);
+ }
+
+ sp<ANetworkSession> netSession = new ANetworkSession;
+ netSession->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<TimeSyncer> handler = new TimeSyncer(netSession, NULL /* notify */);
+ looper->registerHandler(handler);
+
+ if (localPort >= 0) {
+ handler->startServer(localPort);
+ } else {
+ handler->startClient(connectToHost.c_str(), connectToPort);
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ return 0;
+}
+
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
index c947765..52e4e26 100644
--- a/media/libstagefright/wifi-display/wfd.cpp
+++ b/media/libstagefright/wifi-display/wfd.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "wfd"
#include <utils/Log.h>
+#include "sink/WifiDisplaySink.h"
#include "source/WifiDisplaySource.h"
#include <binder/ProcessState.h>
@@ -38,8 +39,12 @@ 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",
+ " %s -c host[:port]\tconnect to wifi source\n"
+ " -u uri \tconnect to an rtsp uri\n"
+ " -l ip[:port] \tlisten on the specified port "
+ " -f(ilename) \tstream media "
+ "(create a sink)\n"
+ " -s(pecial) \trun in 'special' mode\n",
me);
}
@@ -50,7 +55,8 @@ struct RemoteDisplayClient : public BnRemoteDisplayClient {
const sp<IGraphicBufferProducer> &bufferProducer,
uint32_t width,
uint32_t height,
- uint32_t flags);
+ uint32_t flags,
+ uint32_t session);
virtual void onDisplayDisconnected();
virtual void onDisplayError(int32_t error);
@@ -86,9 +92,10 @@ 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);
+ uint32_t flags,
+ uint32_t session) {
+ ALOGI("onDisplayConnected width=%u, height=%u, flags = 0x%08x, session = %d",
+ width, height, flags, session);
if (bufferProducer != NULL) {
mSurfaceTexture = bufferProducer;
@@ -133,28 +140,6 @@ void RemoteDisplayClient::waitUntilDone() {
}
}
-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"));
@@ -163,21 +148,18 @@ static void createSource(const AString &addr, int32_t port) {
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);
+ sp<IRemoteDisplay> display =
+ service->listenForRemoteDisplay(client, iface);
client->waitUntilDone();
display->dispose();
display.clear();
-
- enableAudioSubmix(false /* enable */);
}
static void createFileSource(
@@ -209,14 +191,48 @@ int main(int argc, char **argv) {
DataSource::RegisterDefaultSniffers();
+ AString connectToHost;
+ int32_t connectToPort = -1;
+ AString uri;
+
AString listenOnAddr;
int32_t listenOnPort = -1;
AString path;
+ bool specialMode = false;
+
int res;
- while ((res = getopt(argc, argv, "hl:f:")) >= 0) {
+ while ((res = getopt(argc, argv, "hc:l:u:f:s")) >= 0) {
switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ connectToHost = optarg;
+ connectToPort = WifiDisplaySource::kWifiDisplayDefaultPort;
+ } else {
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 1 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ }
+ break;
+ }
+
+ case 'u':
+ {
+ uri = optarg;
+ break;
+ }
+
case 'f':
{
path = optarg;
@@ -245,6 +261,12 @@ int main(int argc, char **argv) {
break;
}
+ case 's':
+ {
+ specialMode = true;
+ break;
+ }
+
case '?':
case 'h':
default:
@@ -253,6 +275,13 @@ int main(int argc, char **argv) {
}
}
+ if (connectToPort >= 0 && listenOnPort >= 0) {
+ fprintf(stderr,
+ "You can connect to a source or create one, "
+ "but not both at the same time.\n");
+ exit(1);
+ }
+
if (listenOnPort >= 0) {
if (path.empty()) {
createSource(listenOnAddr, listenOnPort);
@@ -263,7 +292,72 @@ int main(int argc, char **argv) {
exit(0);
}
- usage(argv[0]);
+ if (connectToPort < 0 && uri.empty()) {
+ fprintf(stderr,
+ "You need to select either source host or uri.\n");
+
+ exit(1);
+ }
+
+ if (connectToPort >= 0 && !uri.empty()) {
+ fprintf(stderr,
+ "You need to either connect to a wfd host or an rtsp url, "
+ "not both.\n");
+ exit(1);
+ }
+
+ sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient;
+ CHECK_EQ(composerClient->initCheck(), (status_t)OK);
+
+ sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay(
+ ISurfaceComposer::eDisplayIdMain));
+ DisplayInfo info;
+ SurfaceComposerClient::getDisplayInfo(display, &info);
+ ssize_t displayWidth = info.w;
+ ssize_t displayHeight = info.h;
+
+ ALOGV("display is %d x %d\n", displayWidth, displayHeight);
+
+ sp<SurfaceControl> control =
+ composerClient->createSurface(
+ String8("A Surface"),
+ displayWidth,
+ displayHeight,
+ PIXEL_FORMAT_RGB_565,
+ 0);
+
+ CHECK(control != NULL);
+ CHECK(control->isValid());
+
+ SurfaceComposerClient::openGlobalTransaction();
+ CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK);
+ CHECK_EQ(control->show(), (status_t)OK);
+ SurfaceComposerClient::closeGlobalTransaction();
+
+ sp<Surface> surface = control->getSurface();
+ CHECK(surface != NULL);
+
+ sp<ANetworkSession> session = new ANetworkSession;
+ session->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<WifiDisplaySink> sink = new WifiDisplaySink(
+ specialMode ? WifiDisplaySink::FLAG_SPECIAL_MODE : 0 /* flags */,
+ session,
+ surface->getIGraphicBufferProducer());
+
+ looper->registerHandler(sink);
+
+ if (connectToPort >= 0) {
+ sink->start(connectToHost.c_str(), connectToPort);
+ } else {
+ sink->start(uri.c_str());
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ composerClient->dispose();
return 0;
}