summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/media/AudioSystem.h1
-rw-r--r--include/media/IMediaHTTPConnection.h1
-rw-r--r--include/media/IOMX.h1
-rw-r--r--include/media/nbaio/NBLog.h6
-rw-r--r--include/media/stagefright/ACodec.h3
-rw-r--r--include/media/stagefright/CameraSource.h2
-rw-r--r--media/libmedia/Android.mk22
-rw-r--r--media/libmedia/AudioRecord.cpp46
-rw-r--r--media/libmedia/AudioTrack.cpp5
-rw-r--r--media/libmedia/IAudioFlinger.cpp2
-rw-r--r--media/libmedia/IMediaHTTPConnection.cpp21
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.cpp17
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayer.cpp9
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp61
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h5
-rw-r--r--media/libnbaio/Android.mk7
-rw-r--r--media/libnbaio/NBLog.cpp84
-rw-r--r--media/libstagefright/ACodec.cpp36
-rw-r--r--media/libstagefright/CameraSource.cpp27
-rw-r--r--media/libstagefright/MPEG4Extractor.cpp135
-rw-r--r--media/libstagefright/Utils.cpp5
-rw-r--r--media/libstagefright/http/MediaHTTP.cpp4
-rw-r--r--media/libstagefright/httplive/LiveSession.cpp199
-rw-r--r--media/libstagefright/httplive/LiveSession.h31
-rw-r--r--media/libstagefright/httplive/M3UParser.cpp172
-rw-r--r--media/libstagefright/httplive/M3UParser.h10
-rw-r--r--media/libstagefright/omx/GraphicBufferSource.cpp60
-rw-r--r--media/libstagefright/omx/GraphicBufferSource.h13
-rw-r--r--media/libstagefright/omx/OMXNodeInstance.cpp9
-rw-r--r--services/audioflinger/AudioFlinger.cpp7
-rw-r--r--services/audioflinger/AudioFlinger.h10
-rw-r--r--services/audioflinger/AudioResampler.cpp4
-rw-r--r--services/audioflinger/AudioResampler.h32
-rw-r--r--services/audioflinger/AudioResamplerCubic.cpp4
-rw-r--r--services/audioflinger/AudioResamplerDyn.cpp2
-rw-r--r--services/audioflinger/AudioResamplerSinc.cpp2
-rw-r--r--services/audioflinger/RecordTracks.h32
-rw-r--r--services/audioflinger/Threads.cpp689
-rw-r--r--services/audioflinger/Threads.h60
-rw-r--r--services/audioflinger/TrackBase.h4
-rw-r--r--services/audioflinger/Tracks.cpp50
-rw-r--r--services/audioflinger/test-resample.cpp93
-rw-r--r--services/camera/libcameraservice/api1/Camera2Client.cpp14
43 files changed, 1306 insertions, 691 deletions
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index fd86737..28fdfd4 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -118,6 +118,7 @@ public:
static bool routedToA2dpOutput(audio_stream_type_t streamType);
+ // return status NO_ERROR implies *buffSize > 0
static status_t getInputBufferSize(uint32_t sampleRate, audio_format_t format,
audio_channel_mask_t channelMask, size_t* buffSize);
diff --git a/include/media/IMediaHTTPConnection.h b/include/media/IMediaHTTPConnection.h
index e048b64..2a63eb7 100644
--- a/include/media/IMediaHTTPConnection.h
+++ b/include/media/IMediaHTTPConnection.h
@@ -38,6 +38,7 @@ struct IMediaHTTPConnection : public IInterface {
virtual ssize_t readAt(off64_t offset, void *data, size_t size) = 0;
virtual off64_t getSize() = 0;
virtual status_t getMIMEType(String8 *mimeType) = 0;
+ virtual status_t getUri(String8 *uri) = 0;
private:
DISALLOW_EVIL_CONSTRUCTORS(IMediaHTTPConnection);
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index 3db2c38..f6f9e7a 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -144,6 +144,7 @@ public:
INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY, // data is an int64_t
INTERNAL_OPTION_MAX_TIMESTAMP_GAP, // data is int64_t
INTERNAL_OPTION_START_TIME, // data is an int64_t
+ INTERNAL_OPTION_TIME_LAPSE, // data is an int64_t[2]
};
virtual status_t setInternalOption(
node_id node,
diff --git a/include/media/nbaio/NBLog.h b/include/media/nbaio/NBLog.h
index 6d59ea7..bcbbc04 100644
--- a/include/media/nbaio/NBLog.h
+++ b/include/media/nbaio/NBLog.h
@@ -25,6 +25,8 @@
namespace android {
+class String8;
+
class NBLog {
public:
@@ -187,6 +189,10 @@ private:
const Shared* const mShared; // raw pointer to shared memory
const sp<IMemory> mIMemory; // ref-counted version
int32_t mFront; // index of oldest acknowledged Entry
+ int mFd; // file descriptor
+ int mIndent; // indentation level
+
+ void dumpLine(const String8& timestamp, String8& body);
static const size_t kSquashTimestamp = 5; // squash this many or more adjacent timestamps
};
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index e284109..36f2a67 100644
--- a/include/media/stagefright/ACodec.h
+++ b/include/media/stagefright/ACodec.h
@@ -207,6 +207,9 @@ private:
int64_t mRepeatFrameDelayUs;
int64_t mMaxPtsGapUs;
+ int64_t mTimePerFrameUs;
+ int64_t mTimePerCaptureUs;
+
bool mCreateInputBuffersSuspended;
status_t setCyclicIntraMacroblockRefresh(const sp<AMessage> &msg, int32_t mode);
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index 69cfbd0..dd0a106 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -172,7 +172,7 @@ protected:
const sp<IGraphicBufferProducer>& surface,
bool storeMetaDataInVideoBuffers);
- virtual void startCameraRecording();
+ virtual status_t startCameraRecording();
virtual void releaseRecordingFrame(const sp<IMemory>& frame);
// Returns true if need to skip the current frame.
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index fc4b2a5..e0acae6 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -60,16 +60,12 @@ LOCAL_SRC_FILES:= \
LOCAL_SRC_FILES += ../libnbaio/roundup.c
-# for <cutils/atomic-inline.h>
-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 libicui18n libexpat \
libcamera_client libstagefright_foundation \
- libgui libdl libaudioutils
+ libgui libdl libaudioutils libnbaio
+
+LOCAL_STATIC_LIBRARIES += libinstantssq
LOCAL_WHOLE_STATIC_LIBRARY := libmedia_helper
@@ -84,3 +80,15 @@ LOCAL_C_INCLUDES := \
$(call include-path-for, audio-utils)
include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+# for <cutils/atomic-inline.h>
+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"'
+
+LOCAL_MODULE := libinstantssq
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 6ca499b..ce35c31 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -41,30 +41,22 @@ status_t AudioRecord::getMinFrameCount(
return BAD_VALUE;
}
- // default to 0 in case of error
- *frameCount = 0;
-
- size_t size = 0;
+ size_t size;
status_t status = AudioSystem::getInputBufferSize(sampleRate, format, channelMask, &size);
if (status != NO_ERROR) {
- ALOGE("AudioSystem could not query the input buffer size; status %d", status);
- return NO_INIT;
+ ALOGE("AudioSystem could not query the input buffer size for sampleRate %u, format %#x, "
+ "channelMask %#x; status %d", sampleRate, format, channelMask, status);
+ return status;
}
- if (size == 0) {
+ // We double the size of input buffer for ping pong use of record buffer.
+ // Assumes audio_is_linear_pcm(format)
+ if ((*frameCount = (size * 2) / (popcount(channelMask) * audio_bytes_per_sample(format))) == 0) {
ALOGE("Unsupported configuration: sampleRate %u, format %#x, channelMask %#x",
sampleRate, format, channelMask);
return BAD_VALUE;
}
- // We double the size of input buffer for ping pong use of record buffer.
- size <<= 1;
-
- // Assumes audio_is_linear_pcm(format)
- uint32_t channelCount = popcount(channelMask);
- size /= channelCount * audio_bytes_per_sample(format);
-
- *frameCount = size;
return NO_ERROR;
}
@@ -133,6 +125,11 @@ status_t AudioRecord::set(
transfer_type transferType,
audio_input_flags_t flags)
{
+ ALOGV("set(): inputSource %d, sampleRate %u, format %#x, channelMask %#x, frameCount %d, "
+ "notificationFrames %d, sessionId %d, transferType %d, flags %#x",
+ inputSource, sampleRate, format, channelMask, frameCountInt, notificationFrames,
+ sessionId, transferType, flags);
+
switch (transferType) {
case TRANSFER_DEFAULT:
if (cbf == NULL || threadCanCallJava) {
@@ -163,9 +160,6 @@ status_t AudioRecord::set(
}
size_t frameCount = frameCountInt;
- ALOGV("set(): sampleRate %u, channelMask %#x, frameCount %u", sampleRate, channelMask,
- frameCount);
-
AutoMutex lock(mLock);
if (mAudioRecord != 0) {
@@ -209,15 +203,19 @@ status_t AudioRecord::set(
uint32_t channelCount = popcount(channelMask);
mChannelCount = channelCount;
- // Assumes audio_is_linear_pcm(format), else sizeof(uint8_t)
- mFrameSize = channelCount * audio_bytes_per_sample(format);
+ if (audio_is_linear_pcm(format)) {
+ mFrameSize = channelCount * audio_bytes_per_sample(format);
+ } else {
+ mFrameSize = sizeof(uint8_t);
+ }
// validate framecount
- size_t minFrameCount = 0;
+ size_t minFrameCount;
status_t status = AudioRecord::getMinFrameCount(&minFrameCount,
sampleRate, format, channelMask);
if (status != NO_ERROR) {
- ALOGE("getMinFrameCount() failed; status %d", status);
+ ALOGE("getMinFrameCount() failed for sampleRate %u, format %#x, channelMask %#x; status %d",
+ sampleRate, format, channelMask, status);
return status;
}
ALOGV("AudioRecord::set() minFrameCount = %d", minFrameCount);
@@ -462,7 +460,9 @@ status_t AudioRecord::openRecord_l(size_t epoch)
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);
+ ALOGE("Could not get audio input for record source %d, sample rate %u, format %#x, "
+ "channel mask %#x, session %d",
+ mInputSource, mSampleRate, mFormat, mChannelMask, mSessionId);
return BAD_VALUE;
}
{
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 5c62260..46025c0 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -195,6 +195,11 @@ status_t AudioTrack::set(
int uid,
pid_t pid)
{
+ ALOGV("set(): streamType %d, sampleRate %u, format %#x, channelMask %#x, frameCount %d, "
+ "flags #%x, notificationFrames %d, sessionId %d, transferType %d",
+ streamType, sampleRate, format, channelMask, frameCountInt, flags, notificationFrames,
+ sessionId, transferType);
+
switch (transferType) {
case TRANSFER_DEFAULT:
if (sharedBuffer != 0) {
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 7b15e68..e696323 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -988,7 +988,7 @@ status_t BnAudioFlinger::onTransact(
&latency,
flags,
hasOffloadInfo ? &offloadInfo : NULL);
- ALOGV("OPEN_OUTPUT output, %p", output);
+ ALOGV("OPEN_OUTPUT output, %d", output);
reply->writeInt32((int32_t) output);
reply->writeInt32(devices);
reply->writeInt32(samplingRate);
diff --git a/media/libmedia/IMediaHTTPConnection.cpp b/media/libmedia/IMediaHTTPConnection.cpp
index 622d9cf..22c470a 100644
--- a/media/libmedia/IMediaHTTPConnection.cpp
+++ b/media/libmedia/IMediaHTTPConnection.cpp
@@ -33,6 +33,7 @@ enum {
READ_AT,
GET_SIZE,
GET_MIME_TYPE,
+ GET_URI
};
struct BpMediaHTTPConnection : public BpInterface<IMediaHTTPConnection> {
@@ -147,6 +148,26 @@ struct BpMediaHTTPConnection : public BpInterface<IMediaHTTPConnection> {
return OK;
}
+ virtual status_t getUri(String8 *uri) {
+ *uri = String8("");
+
+ Parcel data, reply;
+ data.writeInterfaceToken(
+ IMediaHTTPConnection::getInterfaceDescriptor());
+
+ remote()->transact(GET_URI, data, &reply);
+
+ int32_t exceptionCode = reply.readExceptionCode();
+
+ if (exceptionCode) {
+ return UNKNOWN_ERROR;
+ }
+
+ *uri = String8(reply.readString16());
+
+ return OK;
+ }
+
private:
sp<IMemory> mMemory;
};
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index d377acd..845a589 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -690,10 +690,10 @@ status_t StagefrightRecorder::setParameter(
return setParamTimeLapseEnable(timeLapseEnable);
}
} else if (key == "time-between-time-lapse-frame-capture") {
- int64_t timeBetweenTimeLapseFrameCaptureMs;
- if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureMs)) {
+ int64_t timeBetweenTimeLapseFrameCaptureUs;
+ if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureUs)) {
return setParamTimeBetweenTimeLapseFrameCapture(
- 1000LL * timeBetweenTimeLapseFrameCaptureMs);
+ timeBetweenTimeLapseFrameCaptureUs);
}
} else {
ALOGE("setParameter: failed to find key %s", key.string());
@@ -1436,6 +1436,17 @@ status_t StagefrightRecorder::setupVideoEncoder(
format->setInt32("stride", mVideoWidth);
format->setInt32("slice-height", mVideoWidth);
format->setInt32("color-format", OMX_COLOR_FormatAndroidOpaque);
+
+ // set up time lapse/slow motion for surface source
+ if (mCaptureTimeLapse) {
+ if (mTimeBetweenTimeLapseFrameCaptureUs <= 0) {
+ ALOGE("Invalid mTimeBetweenTimeLapseFrameCaptureUs value: %lld",
+ mTimeBetweenTimeLapseFrameCaptureUs);
+ return BAD_VALUE;
+ }
+ format->setInt64("time-lapse",
+ mTimeBetweenTimeLapseFrameCaptureUs);
+ }
}
format->setInt32("bitrate", mVideoBitRate);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index d47ac98..a750ad0 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -1006,7 +1006,14 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) {
&NuPlayer::performScanSources));
}
- flushDecoder(audio, formatChange);
+ sp<AMessage> newFormat = mSource->getFormat(audio);
+ sp<Decoder> &decoder = audio ? mAudioDecoder : mVideoDecoder;
+ if (formatChange && !decoder->supportsSeamlessFormatChange(newFormat)) {
+ flushDecoder(audio, /* needShutdown = */ true);
+ } else {
+ flushDecoder(audio, /* needShutdown = */ false);
+ err = OK;
+ }
} else {
// This stream is unaffected by the discontinuity
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index 22f699e..2423fd5 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -67,6 +67,7 @@ void NuPlayer::Decoder::configure(const sp<AMessage> &format) {
// queue.
bool needDedicatedLooper = !strncasecmp(mime.c_str(), "video/", 6);
+ mFormat = format;
mCodec = new ACodec;
if (needDedicatedLooper && mCodecLooper == NULL) {
@@ -147,5 +148,65 @@ void NuPlayer::Decoder::initiateShutdown() {
}
}
+bool NuPlayer::Decoder::supportsSeamlessAudioFormatChange(const sp<AMessage> &targetFormat) const {
+ if (targetFormat == NULL) {
+ return true;
+ }
+
+ AString mime;
+ if (!targetFormat->findString("mime", &mime)) {
+ return false;
+ }
+
+ if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+ // field-by-field comparison
+ const char * keys[] = { "channel-count", "sample-rate", "is-adts" };
+ for (unsigned int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) {
+ int32_t oldVal, newVal;
+ if (!mFormat->findInt32(keys[i], &oldVal) || !targetFormat->findInt32(keys[i], &newVal)
+ || oldVal != newVal) {
+ return false;
+ }
+ }
+
+ sp<ABuffer> oldBuf, newBuf;
+ if (mFormat->findBuffer("csd-0", &oldBuf) && targetFormat->findBuffer("csd-0", &newBuf)) {
+ if (oldBuf->size() != newBuf->size()) {
+ return false;
+ }
+ return !memcmp(oldBuf->data(), newBuf->data(), oldBuf->size());
+ }
+ }
+ return false;
+}
+
+bool NuPlayer::Decoder::supportsSeamlessFormatChange(const sp<AMessage> &targetFormat) const {
+ if (mFormat == NULL) {
+ return false;
+ }
+
+ if (targetFormat == NULL) {
+ return true;
+ }
+
+ AString oldMime, newMime;
+ if (!mFormat->findString("mime", &oldMime)
+ || !targetFormat->findString("mime", &newMime)
+ || !(oldMime == newMime)) {
+ return false;
+ }
+
+ bool audio = !strncasecmp(oldMime.c_str(), "audio/", strlen("audio/"));
+ bool seamless;
+ if (audio) {
+ seamless = supportsSeamlessAudioFormatChange(targetFormat);
+ } else {
+ seamless = mCodec != NULL && mCodec->isConfiguredForAdaptivePlayback();
+ }
+
+ ALOGV("%s seamless support for %s", seamless ? "yes" : "no", oldMime.c_str());
+ return seamless;
+}
+
} // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h
index a876148..78ea74a 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h
@@ -36,6 +36,8 @@ struct NuPlayer::Decoder : public AHandler {
void signalResume();
void initiateShutdown();
+ bool supportsSeamlessFormatChange(const sp<AMessage> &to) const;
+
protected:
virtual ~Decoder();
@@ -49,6 +51,7 @@ private:
sp<AMessage> mNotify;
sp<NativeWindowWrapper> mNativeWindow;
+ sp<AMessage> mFormat;
sp<ACodec> mCodec;
sp<ALooper> mCodecLooper;
@@ -59,6 +62,8 @@ private:
void onFillThisBuffer(const sp<AMessage> &msg);
+ bool supportsSeamlessAudioFormatChange(const sp<AMessage> &targetFormat) const;
+
DISALLOW_EVIL_CONSTRUCTORS(Decoder);
};
diff --git a/media/libnbaio/Android.mk b/media/libnbaio/Android.mk
index 69c75b8..9707c4a 100644
--- a/media/libnbaio/Android.mk
+++ b/media/libnbaio/Android.mk
@@ -31,9 +31,8 @@ LOCAL_SHARED_LIBRARIES := \
libcommon_time_client \
libcutils \
libutils \
- liblog \
- libmedia
-# This dependency on libmedia is for SingleStateQueueInstantiations.
-# Consider a separate a library for SingleStateQueueInstantiations.
+ liblog
+
+LOCAL_STATIC_LIBRARIES += libinstantssq
include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libnbaio/NBLog.cpp b/media/libnbaio/NBLog.cpp
index 190824d..96738a7 100644
--- a/media/libnbaio/NBLog.cpp
+++ b/media/libnbaio/NBLog.cpp
@@ -26,6 +26,7 @@
#include <cutils/atomic.h>
#include <media/nbaio/NBLog.h>
#include <utils/Log.h>
+#include <utils/String8.h>
namespace android {
@@ -337,25 +338,25 @@ void NBLog::Reader::dump(int fd, size_t indent)
}
i -= length + 3;
}
- if (i > 0) {
- lost += i;
- if (fd >= 0) {
- fdprintf(fd, "%*swarning: lost %zu bytes worth of events\n", indent, "", lost);
- } else {
- ALOGI("%*swarning: lost %u bytes worth of events\n", indent, "", lost);
- }
+ mFd = fd;
+ mIndent = indent;
+ String8 timestamp, body;
+ lost += i;
+ if (lost > 0) {
+ body.appendFormat("warning: lost %u bytes worth of events", lost);
+ // TODO timestamp empty here, only other choice to wait for the first timestamp event in the
+ // log to push it out. Consider keeping the timestamp/body between calls to readAt().
+ dumpLine(timestamp, body);
}
size_t width = 1;
while (maxSec >= 10) {
++width;
maxSec /= 10;
}
- char prefix[32];
if (maxSec >= 0) {
- snprintf(prefix, sizeof(prefix), "[%*s] ", width + 4, "");
- } else {
- prefix[0] = '\0';
+ timestamp.appendFormat("[%*s]", width + 4, "");
}
+ bool deferredTimestamp = false;
while (i < avail) {
event = (Event) copy[i];
length = copy[i + 1];
@@ -363,11 +364,8 @@ void NBLog::Reader::dump(int fd, size_t indent)
size_t advance = length + 3;
switch (event) {
case EVENT_STRING:
- if (fd >= 0) {
- fdprintf(fd, "%*s%s%.*s\n", indent, "", prefix, length, (const char *) data);
- } else {
- ALOGI("%*s%s%.*s", indent, "", prefix, length, (const char *) data);
- } break;
+ body.appendFormat("%.*s", length, (const char *) data);
+ break;
case EVENT_TIMESTAMP: {
// already checked that length == sizeof(struct timespec);
memcpy(&ts, data, sizeof(struct timespec));
@@ -400,45 +398,53 @@ void NBLog::Reader::dump(int fd, size_t indent)
prevNsec = tsNext.tv_nsec;
}
size_t n = (j - i) / (sizeof(struct timespec) + 3);
+ if (deferredTimestamp) {
+ dumpLine(timestamp, body);
+ deferredTimestamp = false;
+ }
+ timestamp.clear();
if (n >= kSquashTimestamp) {
- if (fd >= 0) {
- fdprintf(fd, "%*s[%d.%03d to .%.03d by .%.03d to .%.03d]\n", indent, "",
- (int) ts.tv_sec, (int) (ts.tv_nsec / 1000000),
- (int) ((ts.tv_nsec + deltaTotal) / 1000000),
- (int) (deltaMin / 1000000), (int) (deltaMax / 1000000));
- } else {
- ALOGI("%*s[%d.%03d to .%.03d by .%.03d to .%.03d]\n", indent, "",
- (int) ts.tv_sec, (int) (ts.tv_nsec / 1000000),
- (int) ((ts.tv_nsec + deltaTotal) / 1000000),
- (int) (deltaMin / 1000000), (int) (deltaMax / 1000000));
- }
+ timestamp.appendFormat("[%d.%03d to .%.03d by .%.03d to .%.03d]",
+ (int) ts.tv_sec, (int) (ts.tv_nsec / 1000000),
+ (int) ((ts.tv_nsec + deltaTotal) / 1000000),
+ (int) (deltaMin / 1000000), (int) (deltaMax / 1000000));
i = j;
advance = 0;
break;
}
- if (fd >= 0) {
- fdprintf(fd, "%*s[%d.%03d]\n", indent, "", (int) ts.tv_sec,
- (int) (ts.tv_nsec / 1000000));
- } else {
- ALOGI("%*s[%d.%03d]", indent, "", (int) ts.tv_sec,
- (int) (ts.tv_nsec / 1000000));
- }
+ timestamp.appendFormat("[%d.%03d]", (int) ts.tv_sec,
+ (int) (ts.tv_nsec / 1000000));
+ deferredTimestamp = true;
} break;
case EVENT_RESERVED:
default:
- if (fd >= 0) {
- fdprintf(fd, "%*s%swarning: unknown event %d\n", indent, "", prefix, event);
- } else {
- ALOGI("%*s%swarning: unknown event %d", indent, "", prefix, event);
- }
+ body.appendFormat("warning: unknown event %d", event);
break;
}
i += advance;
+
+ if (!body.isEmpty()) {
+ dumpLine(timestamp, body);
+ deferredTimestamp = false;
+ }
+ }
+ if (deferredTimestamp) {
+ dumpLine(timestamp, body);
}
// FIXME it would be more efficient to put a char mCopy[256] as a member variable of the dumper
delete[] copy;
}
+void NBLog::Reader::dumpLine(const String8& timestamp, String8& body)
+{
+ if (mFd >= 0) {
+ fdprintf(mFd, "%.*s%s %s\n", mIndent, "", timestamp.string(), body.string());
+ } else {
+ ALOGI("%.*s%s %s", mIndent, "", timestamp.string(), body.string());
+ }
+ body.clear();
+}
+
bool NBLog::Reader::isIMemory(const sp<IMemory>& iMemory) const
{
return iMemory != 0 && mIMemory != 0 && iMemory->pointer() == mIMemory->pointer();
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index ac78d6c..4450d62 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -374,7 +374,9 @@ ACodec::ACodec()
mStoreMetaDataInOutputBuffers(false),
mMetaDataBuffersToSubmit(0),
mRepeatFrameDelayUs(-1ll),
- mMaxPtsGapUs(-1l),
+ mMaxPtsGapUs(-1ll),
+ mTimePerCaptureUs(-1ll),
+ mTimePerFrameUs(-1ll),
mCreateInputBuffersSuspended(false) {
mUninitializedState = new UninitializedState(this);
mLoadedState = new LoadedState(this);
@@ -1119,7 +1121,11 @@ status_t ACodec::configureCodec(
}
if (!msg->findInt64("max-pts-gap-to-encoder", &mMaxPtsGapUs)) {
- mMaxPtsGapUs = -1l;
+ mMaxPtsGapUs = -1ll;
+ }
+
+ if (!msg->findInt64("time-lapse", &mTimePerCaptureUs)) {
+ mTimePerCaptureUs = -1ll;
}
if (!msg->findInt32(
@@ -1916,6 +1922,7 @@ status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg) {
return INVALID_OPERATION;
}
frameRate = (float)tmp;
+ mTimePerFrameUs = (int64_t) (1000000.0f / frameRate);
}
video_def->xFramerate = (OMX_U32)(frameRate * 65536.0f);
@@ -3939,7 +3946,7 @@ void ACodec::LoadedState::onCreateInputSurface(
}
}
- if (err == OK && mCodec->mMaxPtsGapUs > 0l) {
+ if (err == OK && mCodec->mMaxPtsGapUs > 0ll) {
err = mCodec->mOMX->setInternalOption(
mCodec->mNode,
kPortIndexInput,
@@ -3951,8 +3958,27 @@ void ACodec::LoadedState::onCreateInputSurface(
ALOGE("[%s] Unable to configure max timestamp gap (err %d)",
mCodec->mComponentName.c_str(),
err);
- }
- }
+ }
+ }
+
+ if (err == OK && mCodec->mTimePerCaptureUs > 0ll
+ && mCodec->mTimePerFrameUs > 0ll) {
+ int64_t timeLapse[2];
+ timeLapse[0] = mCodec->mTimePerFrameUs;
+ timeLapse[1] = mCodec->mTimePerCaptureUs;
+ err = mCodec->mOMX->setInternalOption(
+ mCodec->mNode,
+ kPortIndexInput,
+ IOMX::INTERNAL_OPTION_TIME_LAPSE,
+ &timeLapse[0],
+ sizeof(timeLapse));
+
+ if (err != OK) {
+ ALOGE("[%s] Unable to configure time lapse (err %d)",
+ mCodec->mComponentName.c_str(),
+ err);
+ }
+ }
if (err == OK && mCodec->mCreateInputBuffersSuspended) {
bool suspend = true;
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index f3ff792..b31e9e8 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -586,14 +586,15 @@ CameraSource::~CameraSource() {
}
}
-void CameraSource::startCameraRecording() {
+status_t CameraSource::startCameraRecording() {
ALOGV("startCameraRecording");
// Reset the identity to the current thread because media server owns the
// camera and recording is started by the applications. The applications
// will connect to the camera in ICameraRecordingProxy::startRecording.
int64_t token = IPCThreadState::self()->clearCallingIdentity();
+ status_t err;
if (mNumInputBuffers > 0) {
- status_t err = mCamera->sendCommand(
+ err = mCamera->sendCommand(
CAMERA_CMD_SET_VIDEO_BUFFER_COUNT, mNumInputBuffers, 0);
// This could happen for CameraHAL1 clients; thus the failure is
@@ -604,17 +605,25 @@ void CameraSource::startCameraRecording() {
}
}
+ err = OK;
if (mCameraFlags & FLAGS_HOT_CAMERA) {
mCamera->unlock();
mCamera.clear();
- CHECK_EQ((status_t)OK,
- mCameraRecordingProxy->startRecording(new ProxyListener(this)));
+ if ((err = mCameraRecordingProxy->startRecording(
+ new ProxyListener(this))) != OK) {
+ ALOGE("Failed to start recording, received error: %s (%d)",
+ strerror(-err), err);
+ }
} else {
mCamera->setListener(new CameraSourceListener(this));
mCamera->startRecording();
- CHECK(mCamera->recordingEnabled());
+ if (!mCamera->recordingEnabled()) {
+ err = -EINVAL;
+ ALOGE("Failed to start recording");
+ }
}
IPCThreadState::self()->restoreCallingIdentity(token);
+ return err;
}
status_t CameraSource::start(MetaData *meta) {
@@ -646,10 +655,12 @@ status_t CameraSource::start(MetaData *meta) {
}
}
- startCameraRecording();
+ status_t err;
+ if ((err = startCameraRecording()) == OK) {
+ mStarted = true;
+ }
- mStarted = true;
- return OK;
+ return err;
}
void CameraSource::stopCameraRecording() {
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 4756b3e..2a3fa04 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -488,12 +488,12 @@ status_t MPEG4Extractor::readMetaData() {
break;
}
uint32_t chunk_type = ntohl(hdr[1]);
- if (chunk_type == FOURCC('s', 'i', 'd', 'x')) {
- // parse the sidx box too
- continue;
- } else if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
+ if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
// store the offset of the first segment
mMoofOffset = offset;
+ } else if (chunk_type != FOURCC('m', 'd', 'a', 't')) {
+ // keep parsing until we get to the data
+ continue;
}
break;
}
@@ -913,6 +913,8 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('e', 'l', 's', 't'):
{
+ *offset += chunk_size;
+
// See 14496-12 8.6.6
uint8_t version;
if (mDataSource->readAt(data_offset, &version, 1) < 1) {
@@ -975,12 +977,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->meta->setInt32(kKeyEncoderPadding, paddingsamples);
}
}
- *offset += chunk_size;
break;
}
case FOURCC('f', 'r', 'm', 'a'):
{
+ *offset += chunk_size;
+
uint32_t original_fourcc;
if (mDataSource->readAt(data_offset, &original_fourcc, 4) < 4) {
return ERROR_IO;
@@ -994,12 +997,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->meta->setInt32(kKeyChannelCount, num_channels);
mLastTrack->meta->setInt32(kKeySampleRate, sample_rate);
}
- *offset += chunk_size;
break;
}
case FOURCC('t', 'e', 'n', 'c'):
{
+ *offset += chunk_size;
+
if (chunk_size < 32) {
return ERROR_MALFORMED;
}
@@ -1044,23 +1048,25 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->meta->setInt32(kKeyCryptoMode, defaultAlgorithmId);
mLastTrack->meta->setInt32(kKeyCryptoDefaultIVSize, defaultIVSize);
mLastTrack->meta->setData(kKeyCryptoKey, 'tenc', defaultKeyId, 16);
- *offset += chunk_size;
break;
}
case FOURCC('t', 'k', 'h', 'd'):
{
+ *offset += chunk_size;
+
status_t err;
if ((err = parseTrackHeader(data_offset, chunk_data_size)) != OK) {
return err;
}
- *offset += chunk_size;
break;
}
case FOURCC('p', 's', 's', 'h'):
{
+ *offset += chunk_size;
+
PsshInfo pssh;
if (mDataSource->readAt(data_offset + 4, &pssh.uuid, 16) < 16) {
@@ -1086,12 +1092,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
}
mPssh.push_back(pssh);
- *offset += chunk_size;
break;
}
case FOURCC('m', 'd', 'h', 'd'):
{
+ *offset += chunk_size;
+
if (chunk_data_size < 4) {
return ERROR_MALFORMED;
}
@@ -1172,7 +1179,6 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->meta->setCString(
kKeyMediaLanguage, lang_code);
- *offset += chunk_size;
break;
}
@@ -1339,11 +1345,12 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->sampleTable->setChunkOffsetParams(
chunk_type, data_offset, chunk_data_size);
+ *offset += chunk_size;
+
if (err != OK) {
return err;
}
- *offset += chunk_size;
break;
}
@@ -1353,11 +1360,12 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->sampleTable->setSampleToChunkParams(
data_offset, chunk_data_size);
+ *offset += chunk_size;
+
if (err != OK) {
return err;
}
- *offset += chunk_size;
break;
}
@@ -1368,6 +1376,8 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->sampleTable->setSampleSizeParams(
chunk_type, data_offset, chunk_data_size);
+ *offset += chunk_size;
+
if (err != OK) {
return err;
}
@@ -1408,7 +1418,6 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
}
mLastTrack->meta->setInt32(kKeyMaxInputSize, max_size);
}
- *offset += chunk_size;
// NOTE: setting another piece of metadata invalidates any pointers (such as the
// mimetype) previously obtained, so don't cache them.
@@ -1432,6 +1441,8 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('s', 't', 't', 's'):
{
+ *offset += chunk_size;
+
status_t err =
mLastTrack->sampleTable->setTimeToSampleParams(
data_offset, chunk_data_size);
@@ -1440,12 +1451,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
return err;
}
- *offset += chunk_size;
break;
}
case FOURCC('c', 't', 't', 's'):
{
+ *offset += chunk_size;
+
status_t err =
mLastTrack->sampleTable->setCompositionTimeToSampleParams(
data_offset, chunk_data_size);
@@ -1454,12 +1466,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
return err;
}
- *offset += chunk_size;
break;
}
case FOURCC('s', 't', 's', 's'):
{
+ *offset += chunk_size;
+
status_t err =
mLastTrack->sampleTable->setSyncSampleParams(
data_offset, chunk_data_size);
@@ -1468,13 +1481,14 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
return err;
}
- *offset += chunk_size;
break;
}
// @xyz
case FOURCC('\xA9', 'x', 'y', 'z'):
{
+ *offset += chunk_size;
+
// Best case the total data length inside "@xyz" box
// would be 8, for instance "@xyz" + "\x00\x04\x15\xc7" + "0+0/",
// where "\x00\x04" is the text string length with value = 4,
@@ -1503,12 +1517,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
buffer[location_length] = '\0';
mFileMetaData->setCString(kKeyLocation, buffer);
- *offset += chunk_size;
break;
}
case FOURCC('e', 's', 'd', 's'):
{
+ *offset += chunk_size;
+
if (chunk_data_size < 4) {
return ERROR_MALFORMED;
}
@@ -1546,12 +1561,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
}
}
- *offset += chunk_size;
break;
}
case FOURCC('a', 'v', 'c', 'C'):
{
+ *offset += chunk_size;
+
sp<ABuffer> buffer = new ABuffer(chunk_data_size);
if (mDataSource->readAt(
@@ -1562,12 +1578,12 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->meta->setData(
kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);
- *offset += chunk_size;
break;
}
case FOURCC('d', '2', '6', '3'):
{
+ *offset += chunk_size;
/*
* d263 contains a fixed 7 bytes part:
* vendor - 4 bytes
@@ -1593,7 +1609,6 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->meta->setData(kKeyD263, kTypeD263, buffer, chunk_data_size);
- *offset += chunk_size;
break;
}
@@ -1601,11 +1616,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
{
uint8_t buffer[4];
if (chunk_data_size < (off64_t)sizeof(buffer)) {
+ *offset += chunk_size;
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
data_offset, buffer, 4) < 4) {
+ *offset += chunk_size;
return ERROR_IO;
}
@@ -1639,6 +1656,8 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('n', 'a', 'm', 'e'):
case FOURCC('d', 'a', 't', 'a'):
{
+ *offset += chunk_size;
+
if (mPath.size() == 6 && underMetaDataPath(mPath)) {
status_t err = parseITunesMetaData(data_offset, chunk_data_size);
@@ -1647,12 +1666,13 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
}
}
- *offset += chunk_size;
break;
}
case FOURCC('m', 'v', 'h', 'd'):
{
+ *offset += chunk_size;
+
if (chunk_data_size < 24) {
return ERROR_MALFORMED;
}
@@ -1680,7 +1700,6 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mFileMetaData->setCString(kKeyDate, s.string());
- *offset += chunk_size;
break;
}
@@ -1701,6 +1720,8 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('h', 'd', 'l', 'r'):
{
+ *offset += chunk_size;
+
uint32_t buffer;
if (mDataSource->readAt(
data_offset + 8, &buffer, 4) < 4) {
@@ -1715,7 +1736,6 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP);
}
- *offset += chunk_size;
break;
}
@@ -1740,6 +1760,8 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
delete[] buffer;
buffer = NULL;
+ // advance read pointer so we don't end up reading this again
+ *offset += chunk_size;
return ERROR_IO;
}
@@ -1754,6 +1776,8 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('c', 'o', 'v', 'r'):
{
+ *offset += chunk_size;
+
if (mFileMetaData != NULL) {
ALOGV("chunk_data_size = %lld and data_offset = %lld",
chunk_data_size, data_offset);
@@ -1768,7 +1792,6 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
buffer->data() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
}
- *offset += chunk_size;
break;
}
@@ -1779,25 +1802,27 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('a', 'l', 'b', 'm'):
case FOURCC('y', 'r', 'r', 'c'):
{
+ *offset += chunk_size;
+
status_t err = parse3GPPMetaData(data_offset, chunk_data_size, depth);
if (err != OK) {
return err;
}
- *offset += chunk_size;
break;
}
case FOURCC('I', 'D', '3', '2'):
{
+ *offset += chunk_size;
+
if (chunk_data_size < 6) {
return ERROR_MALFORMED;
}
parseID3v2MetaData(data_offset + 6);
- *offset += chunk_size;
break;
}
@@ -1921,9 +1946,10 @@ status_t MPEG4Extractor::parseSegmentIndex(off64_t offset, size_t size) {
ALOGW("sub-sidx boxes not supported yet");
}
bool sap = d3 & 0x80000000;
- bool saptype = d3 >> 28;
- if (!sap || saptype > 2) {
- ALOGW("not a stream access point, or unsupported type");
+ uint32_t saptype = (d3 >> 28) & 7;
+ if (!sap || (saptype != 1 && saptype != 2)) {
+ // type 1 and 2 are sync samples
+ ALOGW("not a stream access point, or unsupported type: %08x", d3);
}
total_duration += d2;
offset += 12;
@@ -2899,9 +2925,20 @@ status_t MPEG4Source::parseChunk(off64_t *offset) {
}
}
if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
- // *offset points to the mdat box following this moof
- parseChunk(offset); // doesn't actually parse it, just updates offset
- mNextMoofOffset = *offset;
+ // *offset points to the box following this moof. Find the next moof from there.
+
+ while (true) {
+ if (mDataSource->readAt(*offset, hdr, 8) < 8) {
+ return ERROR_END_OF_STREAM;
+ }
+ chunk_size = ntohl(hdr[0]);
+ chunk_type = ntohl(hdr[1]);
+ if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
+ mNextMoofOffset = *offset;
+ break;
+ }
+ *offset += chunk_size;
+ }
}
break;
}
@@ -3706,7 +3743,7 @@ status_t MPEG4Source::fragmentedRead(
const SidxEntry *se = &mSegments[i];
if (totalTime + se->mDurationUs > seekTimeUs) {
// The requested time is somewhere in this segment
- if ((mode == ReadOptions::SEEK_NEXT_SYNC) ||
+ if ((mode == ReadOptions::SEEK_NEXT_SYNC && seekTimeUs > totalTime) ||
(mode == ReadOptions::SEEK_CLOSEST_SYNC &&
(seekTimeUs - totalTime) > (totalTime + se->mDurationUs - seekTimeUs))) {
// requested next sync, or closest sync and it was closer to the end of
@@ -3719,11 +3756,19 @@ status_t MPEG4Source::fragmentedRead(
totalTime += se->mDurationUs;
totalOffset += se->mSize;
}
- mCurrentMoofOffset = totalOffset;
- mCurrentSamples.clear();
- mCurrentSampleIndex = 0;
- parseChunk(&totalOffset);
- mCurrentTime = totalTime * mTimescale / 1000000ll;
+ mCurrentMoofOffset = totalOffset;
+ mCurrentSamples.clear();
+ mCurrentSampleIndex = 0;
+ parseChunk(&totalOffset);
+ mCurrentTime = totalTime * mTimescale / 1000000ll;
+ } else {
+ // without sidx boxes, we can only seek to 0
+ mCurrentMoofOffset = mFirstMoofOffset;
+ mCurrentSamples.clear();
+ mCurrentSampleIndex = 0;
+ off64_t tmp = mCurrentMoofOffset;
+ parseChunk(&tmp);
+ mCurrentTime = 0;
}
if (mBuffer != NULL) {
@@ -3743,16 +3788,18 @@ status_t MPEG4Source::fragmentedRead(
newBuffer = true;
if (mCurrentSampleIndex >= mCurrentSamples.size()) {
- // move to next fragment
- Sample lastSample = mCurrentSamples[mCurrentSamples.size() - 1];
- off64_t nextMoof = mNextMoofOffset; // lastSample.offset + lastSample.size;
+ // move to next fragment if there is one
+ if (mNextMoofOffset <= mCurrentMoofOffset) {
+ return ERROR_END_OF_STREAM;
+ }
+ off64_t nextMoof = mNextMoofOffset;
mCurrentMoofOffset = nextMoof;
mCurrentSamples.clear();
mCurrentSampleIndex = 0;
parseChunk(&nextMoof);
- if (mCurrentSampleIndex >= mCurrentSamples.size()) {
- return ERROR_END_OF_STREAM;
- }
+ if (mCurrentSampleIndex >= mCurrentSamples.size()) {
+ return ERROR_END_OF_STREAM;
+ }
}
const Sample *smpl = &mCurrentSamples[mCurrentSampleIndex];
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 216a329..451e907 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -452,6 +452,11 @@ void convertMessageToMetaData(const sp<AMessage> &msg, sp<MetaData> &meta) {
}
}
+ int32_t timeScale;
+ if (msg->findInt32("time-scale", &timeScale)) {
+ meta->setInt32(kKeyTimeScale, timeScale);
+ }
+
// XXX TODO add whatever other keys there are
#if 0
diff --git a/media/libstagefright/http/MediaHTTP.cpp b/media/libstagefright/http/MediaHTTP.cpp
index 157d967..2d29913 100644
--- a/media/libstagefright/http/MediaHTTP.cpp
+++ b/media/libstagefright/http/MediaHTTP.cpp
@@ -171,6 +171,10 @@ void MediaHTTP::getDrmInfo(
}
String8 MediaHTTP::getUri() {
+ String8 uri;
+ if (OK == mHTTPConnection->getUri(&uri)) {
+ return uri;
+ }
return String8(mLastURI.c_str());
}
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index f0a1c36..95779c4 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -61,14 +61,14 @@ LiveSession::LiveSession(
mRealTimeBaseUs(0ll),
mReconfigurationInProgress(false),
mDisconnectReplyID(0) {
- mPacketSources.add(
- STREAMTYPE_AUDIO, new AnotherPacketSource(NULL /* meta */));
- mPacketSources.add(
- STREAMTYPE_VIDEO, new AnotherPacketSource(NULL /* meta */));
+ mStreams[kAudioIndex] = StreamItem("audio");
+ mStreams[kVideoIndex] = StreamItem("video");
+ mStreams[kSubtitleIndex] = StreamItem("subtitle");
- mPacketSources.add(
- STREAMTYPE_SUBTITLES, new AnotherPacketSource(NULL /* meta */));
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ mPacketSources.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
+ }
}
LiveSession::~LiveSession() {
@@ -369,6 +369,12 @@ int LiveSession::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b)
return 1;
}
+// static
+LiveSession::StreamType LiveSession::indexToType(int idx) {
+ CHECK(idx >= 0 && idx < kMaxStreams);
+ return (StreamType)(1 << idx);
+}
+
void LiveSession::onConnect(const sp<AMessage> &msg) {
AString url;
CHECK(msg->findString("url", &url));
@@ -527,7 +533,8 @@ status_t LiveSession::fetchFile(
const char *url, sp<ABuffer> *out,
int64_t range_offset, int64_t range_length,
uint32_t block_size, /* download block size */
- sp<DataSource> *source /* to return and reuse source */) {
+ sp<DataSource> *source, /* to return and reuse source */
+ String8 *actualUrl) {
off64_t size;
sp<DataSource> temp_source;
if (source == NULL) {
@@ -623,6 +630,12 @@ status_t LiveSession::fetchFile(
}
*out = buffer;
+ if (actualUrl != NULL) {
+ *actualUrl = (*source)->getUri();
+ if (actualUrl->isEmpty()) {
+ *actualUrl = url;
+ }
+ }
return OK;
}
@@ -634,7 +647,8 @@ sp<M3UParser> LiveSession::fetchPlaylist(
*unchanged = false;
sp<ABuffer> buffer;
- status_t err = fetchFile(url, &buffer);
+ String8 actualUrl;
+ status_t err = fetchFile(url, &buffer, 0, -1, 0, NULL, &actualUrl);
if (err != OK) {
return NULL;
@@ -665,7 +679,7 @@ sp<M3UParser> LiveSession::fetchPlaylist(
#endif
sp<M3UParser> playlist =
- new M3UParser(url, buffer->data(), buffer->size());
+ new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
if (playlist->initCheck() != OK) {
ALOGE("failed to parse .m3u8 playlist");
@@ -850,19 +864,11 @@ void LiveSession::changeConfiguration(
uint32_t streamMask = 0;
- AString audioURI;
- if (mPlaylist->getAudioURI(item.mPlaylistIndex, &audioURI)) {
- streamMask |= STREAMTYPE_AUDIO;
- }
-
- AString videoURI;
- if (mPlaylist->getVideoURI(item.mPlaylistIndex, &videoURI)) {
- streamMask |= STREAMTYPE_VIDEO;
- }
-
- AString subtitleURI;
- if (mPlaylist->getSubtitleURI(item.mPlaylistIndex, &subtitleURI)) {
- streamMask |= STREAMTYPE_SUBTITLES;
+ AString URIs[kMaxStreams];
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (mPlaylist->getTypeURI(item.mPlaylistIndex, mStreams[i].mType, &URIs[i])) {
+ streamMask |= indexToType(i);
+ }
}
// Step 1, stop and discard fetchers that are no longer needed.
@@ -874,10 +880,10 @@ void LiveSession::changeConfiguration(
// 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;
+ for (size_t j = 0; j < kMaxStreams; ++j) {
+ if ((streamMask & indexToType(j)) && uri == URIs[j]) {
+ discardFetcher = false;
+ }
}
}
@@ -891,14 +897,10 @@ void LiveSession::changeConfiguration(
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());
- }
- if (streamMask & STREAMTYPE_VIDEO) {
- msg->setString("videoURI", videoURI.c_str());
- }
- if (streamMask & STREAMTYPE_SUBTITLES) {
- msg->setString("subtitleURI", subtitleURI.c_str());
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (streamMask & indexToType(i)) {
+ msg->setString(mStreams[i].uriKey().c_str(), URIs[i].c_str());
+ }
}
// Every time a fetcher acknowledges the stopAsync or pauseAsync request
@@ -929,18 +931,13 @@ void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
uint32_t streamMask;
CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
- AString audioURI, videoURI, subtitleURI;
- if (streamMask & STREAMTYPE_AUDIO) {
- CHECK(msg->findString("audioURI", &audioURI));
- ALOGV("audioURI = '%s'", audioURI.c_str());
- }
- if (streamMask & STREAMTYPE_VIDEO) {
- CHECK(msg->findString("videoURI", &videoURI));
- ALOGV("videoURI = '%s'", videoURI.c_str());
- }
- if (streamMask & STREAMTYPE_SUBTITLES) {
- CHECK(msg->findString("subtitleURI", &subtitleURI));
- ALOGV("subtitleURI = '%s'", subtitleURI.c_str());
+ AString URIs[kMaxStreams];
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (streamMask & indexToType(i)) {
+ const AString &uriKey = mStreams[i].uriKey();
+ CHECK(msg->findString(uriKey.c_str(), &URIs[i]));
+ ALOGV("%s = '%s'", uriKey.c_str(), URIs[i].c_str());
+ }
}
// Determine which decoders to shutdown on the player side,
@@ -950,15 +947,12 @@ void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
// 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;
+ for (size_t i = 0; i < kMaxStreams && i != kSubtitleIndex; ++i) {
+ if (((mStreamMask & streamMask & indexToType(i))
+ && !(URIs[i] == mStreams[i].mUri))
+ || (mStreamMask & ~streamMask & indexToType(i))) {
+ changedMask |= indexToType(i);
+ }
}
if (changedMask == 0) {
@@ -990,15 +984,10 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
uint32_t streamMask;
CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
- 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));
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (streamMask & indexToType(i)) {
+ CHECK(msg->findString(mStreams[i].uriKey().c_str(), &mStreams[i].mUri));
+ }
}
int64_t timeUs;
@@ -1010,9 +999,6 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
mRealTimeBaseUs = ALooper::GetNowUs() - timeUs;
mStreamMask = streamMask;
- mAudioURI = audioURI;
- mVideoURI = videoURI;
- mSubtitleURI = subtitleURI;
// Resume all existing fetchers and assign them packet sources.
for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
@@ -1020,22 +1006,12 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
uint32_t resumeMask = 0;
- sp<AnotherPacketSource> audioSource;
- if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
- audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
- resumeMask |= STREAMTYPE_AUDIO;
- }
-
- sp<AnotherPacketSource> videoSource;
- if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
- videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
- resumeMask |= STREAMTYPE_VIDEO;
- }
-
- sp<AnotherPacketSource> subtitleSource;
- if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
- subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
- resumeMask |= STREAMTYPE_SUBTITLES;
+ sp<AnotherPacketSource> sources[kMaxStreams];
+ for (size_t j = 0; j < kMaxStreams; ++j) {
+ if ((streamMask & indexToType(j)) && uri == mStreams[j].mUri) {
+ sources[j] = mPacketSources.valueFor(indexToType(j));
+ resumeMask |= indexToType(j);
+ }
}
CHECK_NE(resumeMask, 0u);
@@ -1045,7 +1021,7 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
streamMask &= ~resumeMask;
mFetcherInfos.valueAt(i).mFetcher->startAsync(
- audioSource, videoSource, subtitleSource);
+ sources[kAudioIndex], sources[kVideoIndex], sources[kSubtitleIndex]);
}
// streamMask now only contains the types that need a new fetcher created.
@@ -1054,52 +1030,33 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
ALOGV("creating new fetchers for mask 0x%08x", streamMask);
}
- while (streamMask != 0) {
- StreamType streamType = (StreamType)(streamMask & ~(streamMask - 1));
+ for (size_t i = 0; i < kMaxStreams; i++) {
+ if (!(indexToType(i) & streamMask)) {
+ continue;
+ }
AString uri;
- switch (streamType) {
- case STREAMTYPE_AUDIO:
- uri = audioURI;
- break;
- case STREAMTYPE_VIDEO:
- uri = videoURI;
- break;
- case STREAMTYPE_SUBTITLES:
- uri = subtitleURI;
- break;
- default:
- TRESPASS();
- }
+ uri = mStreams[i].mUri;
sp<PlaylistFetcher> fetcher = addFetcher(uri.c_str());
CHECK(fetcher != NULL);
- sp<AnotherPacketSource> audioSource;
- if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
- audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
- audioSource->clear();
-
- streamMask &= ~STREAMTYPE_AUDIO;
- }
-
- sp<AnotherPacketSource> videoSource;
- if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
- videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
- videoSource->clear();
-
- streamMask &= ~STREAMTYPE_VIDEO;
- }
+ sp<AnotherPacketSource> sources[kMaxStreams];
+ // TRICKY: looping from i as earlier streams are already removed from streamMask
+ for (size_t j = i; j < kMaxStreams; ++j) {
+ if ((streamMask & indexToType(j)) && uri == mStreams[j].mUri) {
+ sources[j] = mPacketSources.valueFor(indexToType(j));
+ sources[j]->clear();
- sp<AnotherPacketSource> subtitleSource;
- if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
- subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
- subtitleSource->clear();
-
- streamMask &= ~STREAMTYPE_SUBTITLES;
+ streamMask &= ~indexToType(j);
+ }
}
- fetcher->startAsync(audioSource, videoSource, subtitleSource, timeUs);
+ fetcher->startAsync(
+ sources[kAudioIndex],
+ sources[kVideoIndex],
+ sources[kSubtitleIndex],
+ timeUs);
}
// All fetchers have now been started, the configuration change
diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h
index 00569be..c4d125c 100644
--- a/media/libstagefright/httplive/LiveSession.h
+++ b/media/libstagefright/httplive/LiveSession.h
@@ -44,10 +44,17 @@ struct LiveSession : public AHandler {
uint32_t flags,
const sp<IMediaHTTPService> &httpService);
+ enum StreamIndex {
+ kAudioIndex = 0,
+ kVideoIndex = 1,
+ kSubtitleIndex = 2,
+ kMaxStreams = 3,
+ };
+
enum StreamType {
- STREAMTYPE_AUDIO = 1,
- STREAMTYPE_VIDEO = 2,
- STREAMTYPE_SUBTITLES = 4,
+ STREAMTYPE_AUDIO = 1 << kAudioIndex,
+ STREAMTYPE_VIDEO = 1 << kVideoIndex,
+ STREAMTYPE_SUBTITLES = 1 << kSubtitleIndex,
};
status_t dequeueAccessUnit(StreamType stream, sp<ABuffer> *accessUnit);
@@ -107,6 +114,19 @@ private:
bool mIsPrepared;
};
+ struct StreamItem {
+ const char *mType;
+ AString mUri;
+ StreamItem() : mType("") {}
+ StreamItem(const char *type) : mType(type) {}
+ AString uriKey() {
+ AString key(mType);
+ key.append("URI");
+ return key;
+ }
+ };
+ StreamItem mStreams[kMaxStreams];
+
sp<AMessage> mNotify;
uint32_t mFlags;
sp<IMediaHTTPService> mHTTPService;
@@ -124,7 +144,6 @@ private:
sp<M3UParser> mPlaylist;
KeyedVector<AString, FetcherInfo> mFetcherInfos;
- AString mAudioURI, mVideoURI, mSubtitleURI;
uint32_t mStreamMask;
KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources;
@@ -164,7 +183,8 @@ private:
/* download block size */
uint32_t block_size = 0,
/* reuse DataSource if doing partial fetch */
- sp<DataSource> *source = NULL);
+ sp<DataSource> *source = NULL,
+ String8 *actualUrl = NULL);
sp<M3UParser> fetchPlaylist(
const char *url, uint8_t *curPlaylistHash, bool *unchanged);
@@ -172,6 +192,7 @@ private:
size_t getBandwidthIndex();
static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
+ static StreamType indexToType(int idx);
void changeConfiguration(
int64_t timeUs, size_t bandwidthIndex, bool pickTrack = false);
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index 39d80fc..587a6d5 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -24,6 +24,7 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
#include <media/mediaplayer.h>
namespace android {
@@ -352,9 +353,27 @@ bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const {
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);
+ AString codecs;
+ if (!meta->findString("codecs", &codecs)) {
+ // Assume media without any more specific attribute contains
+ // audio and video, but no subtitles.
+ return !strcmp("audio", key) || !strcmp("video", key);
+ } else {
+ // Split the comma separated list of codecs.
+ size_t offset = 0;
+ ssize_t commaPos = -1;
+ codecs.append(',');
+ while ((commaPos = codecs.find(",", offset)) >= 0) {
+ AString codec(codecs, offset, commaPos - offset);
+ // return true only if a codec of type `key` ("audio"/"video")
+ // is found.
+ if (codecIsType(codec, key)) {
+ return true;
+ }
+ offset = commaPos + 1;
+ }
+ return false;
+ }
}
sp<MediaGroup> group = mMediaGroups.valueFor(groupID);
@@ -369,18 +388,6 @@ bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const {
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();
@@ -694,12 +701,22 @@ status_t M3UParser::parseStreamInf(
*meta = new AMessage;
}
(*meta)->setInt32("bandwidth", x);
+ } else if (!strcasecmp("codecs", key.c_str())) {
+ if (!isQuotedString(val)) {
+ ALOGE("Expected quoted string for %s attribute, "
+ "got '%s' instead.",
+ key.c_str(), val.c_str());;
+
+ return ERROR_MALFORMED;
+ }
+
+ key.tolower();
+ const AString &codecs = unquoteString(val);
+ (*meta)->setString(key.c_str(), codecs.c_str());
} 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] != '"') {
+ if (!isQuotedString(val)) {
ALOGE("Expected quoted string for %s attribute, "
"got '%s' instead.",
key.c_str(), val.c_str());
@@ -707,7 +724,7 @@ status_t M3UParser::parseStreamInf(
return ERROR_MALFORMED;
}
- AString groupID(val, 1, val.size() - 2);
+ const AString &groupID = unquoteString(val);
ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
if (groupIndex < 0) {
@@ -1096,4 +1113,121 @@ status_t M3UParser::ParseDouble(const char *s, double *x) {
return OK;
}
+// static
+bool M3UParser::isQuotedString(const AString &str) {
+ if (str.size() < 2
+ || str.c_str()[0] != '"'
+ || str.c_str()[str.size() - 1] != '"') {
+ return false;
+ }
+ return true;
+}
+
+// static
+AString M3UParser::unquoteString(const AString &str) {
+ if (!isQuotedString(str)) {
+ return str;
+ }
+ return AString(str, 1, str.size() - 2);
+}
+
+// static
+bool M3UParser::codecIsType(const AString &codec, const char *type) {
+ if (codec.size() < 4) {
+ return false;
+ }
+ const char *c = codec.c_str();
+ switch (FOURCC(c[0], c[1], c[2], c[3])) {
+ // List extracted from http://www.mp4ra.org/codecs.html
+ case 'ac-3':
+ case 'alac':
+ case 'dra1':
+ case 'dtsc':
+ case 'dtse':
+ case 'dtsh':
+ case 'dtsl':
+ case 'ec-3':
+ case 'enca':
+ case 'g719':
+ case 'g726':
+ case 'm4ae':
+ case 'mlpa':
+ case 'mp4a':
+ case 'raw ':
+ case 'samr':
+ case 'sawb':
+ case 'sawp':
+ case 'sevc':
+ case 'sqcp':
+ case 'ssmv':
+ case 'twos':
+ case 'agsm':
+ case 'alaw':
+ case 'dvi ':
+ case 'fl32':
+ case 'fl64':
+ case 'ima4':
+ case 'in24':
+ case 'in32':
+ case 'lpcm':
+ case 'Qclp':
+ case 'QDM2':
+ case 'QDMC':
+ case 'ulaw':
+ case 'vdva':
+ return !strcmp("audio", type);
+
+ case 'avc1':
+ case 'avc2':
+ case 'avcp':
+ case 'drac':
+ case 'encv':
+ case 'mjp2':
+ case 'mp4v':
+ case 'mvc1':
+ case 'mvc2':
+ case 'resv':
+ case 's263':
+ case 'svc1':
+ case 'vc-1':
+ case 'CFHD':
+ case 'civd':
+ case 'DV10':
+ case 'dvh5':
+ case 'dvh6':
+ case 'dvhp':
+ case 'DVOO':
+ case 'DVOR':
+ case 'DVTV':
+ case 'DVVT':
+ case 'flic':
+ case 'gif ':
+ case 'h261':
+ case 'h263':
+ case 'HD10':
+ case 'jpeg':
+ case 'M105':
+ case 'mjpa':
+ case 'mjpb':
+ case 'png ':
+ case 'PNTG':
+ case 'rle ':
+ case 'rpza':
+ case 'Shr0':
+ case 'Shr1':
+ case 'Shr2':
+ case 'Shr3':
+ case 'Shr4':
+ case 'SVQ1':
+ case 'SVQ3':
+ case 'tga ':
+ case 'tiff':
+ case 'WRLE':
+ return !strcmp("video", type);
+
+ default:
+ return false;
+ }
+}
+
} // namespace android
diff --git a/media/libstagefright/httplive/M3UParser.h b/media/libstagefright/httplive/M3UParser.h
index 5248004..ccd6556 100644
--- a/media/libstagefright/httplive/M3UParser.h
+++ b/media/libstagefright/httplive/M3UParser.h
@@ -45,9 +45,7 @@ struct M3UParser : public RefBase {
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;
+ bool getTypeURI(size_t index, const char *key, AString *uri) const;
protected:
virtual ~M3UParser();
@@ -95,11 +93,13 @@ private:
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);
+ static bool isQuotedString(const AString &str);
+ static AString unquoteString(const AString &str);
+ static bool codecIsType(const AString &codec, const char *type);
+
DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
};
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/omx/GraphicBufferSource.cpp
index 7672204..b81b116 100644
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ b/media/libstagefright/omx/GraphicBufferSource.cpp
@@ -51,7 +51,11 @@ GraphicBufferSource::GraphicBufferSource(OMXNodeInstance* nodeInstance,
mLatestSubmittedBufferId(-1),
mLatestSubmittedBufferFrameNum(0),
mLatestSubmittedBufferUseCount(0),
- mRepeatBufferDeferred(false) {
+ mRepeatBufferDeferred(false),
+ mTimePerCaptureUs(-1ll),
+ mTimePerFrameUs(-1ll),
+ mPrevCaptureUs(-1ll),
+ mPrevFrameUs(-1ll) {
ALOGV("GraphicBufferSource w=%u h=%u c=%u",
bufferWidth, bufferHeight, bufferCount);
@@ -560,7 +564,30 @@ status_t GraphicBufferSource::signalEndOfInputStream() {
int64_t GraphicBufferSource::getTimestamp(const BufferQueue::BufferItem &item) {
int64_t timeUs = item.mTimestamp / 1000;
- if (mMaxTimestampGapUs > 0ll) {
+ if (mTimePerCaptureUs > 0ll) {
+ // Time lapse or slow motion mode
+ if (mPrevCaptureUs < 0ll) {
+ // first capture
+ mPrevCaptureUs = timeUs;
+ mPrevFrameUs = timeUs;
+ } else {
+ // snap to nearest capture point
+ int64_t nFrames = (timeUs + mTimePerCaptureUs / 2 - mPrevCaptureUs)
+ / mTimePerCaptureUs;
+ if (nFrames <= 0) {
+ // skip this frame as it's too close to previous capture
+ ALOGV("skipping frame, timeUs %lld", timeUs);
+ return -1;
+ }
+ mPrevCaptureUs = mPrevCaptureUs + nFrames * mTimePerCaptureUs;
+ mPrevFrameUs += mTimePerFrameUs * nFrames;
+ }
+
+ ALOGV("timeUs %lld, captureUs %lld, frameUs %lld",
+ timeUs, mPrevCaptureUs, mPrevFrameUs);
+
+ return mPrevFrameUs;
+ } else if (mMaxTimestampGapUs > 0ll) {
/* Cap timestamp gap between adjacent frames to specified max
*
* In the scenario of cast mirroring, encoding could be suspended for
@@ -574,7 +601,7 @@ int64_t GraphicBufferSource::getTimestamp(const BufferQueue::BufferItem &item) {
if (originalTimeUs < mPrevOriginalTimeUs) {
// Drop the frame if it's going backward in time. Bad timestamp
// could disrupt encoder's rate control completely.
- ALOGV("Dropping frame that's going backward in time");
+ ALOGW("Dropping frame that's going backward in time");
return -1;
}
int64_t timestampGapUs = originalTimeUs - mPrevOriginalTimeUs;
@@ -593,6 +620,12 @@ int64_t GraphicBufferSource::getTimestamp(const BufferQueue::BufferItem &item) {
status_t GraphicBufferSource::submitBuffer_l(
const BufferQueue::BufferItem &item, int cbi) {
ALOGV("submitBuffer_l cbi=%d", cbi);
+
+ int64_t timeUs = getTimestamp(item);
+ if (timeUs < 0ll) {
+ return UNKNOWN_ERROR;
+ }
+
CodecBuffer& codecBuffer(mCodecBuffers.editItemAt(cbi));
codecBuffer.mGraphicBuffer = mBufferSlot[item.mBuf];
codecBuffer.mBuf = item.mBuf;
@@ -606,12 +639,6 @@ status_t GraphicBufferSource::submitBuffer_l(
memcpy(data, &type, 4);
memcpy(data + 4, &handle, sizeof(buffer_handle_t));
- int64_t timeUs = getTimestamp(item);
- if (timeUs < 0ll) {
- ALOGE("Dropping frame with bad timestamp");
- return UNKNOWN_ERROR;
- }
-
status_t err = mNodeInstance->emptyDirectBuffer(header, 0,
4 + sizeof(buffer_handle_t), OMX_BUFFERFLAG_ENDOFFRAME,
timeUs);
@@ -711,7 +738,7 @@ void GraphicBufferSource::onFrameAvailable() {
// If this is the first time we're seeing this buffer, add it to our
// slot table.
if (item.mGraphicBuffer != NULL) {
- ALOGV("fillCodecBuffer_l: setting mBufferSlot %d", item.mBuf);
+ ALOGV("onFrameAvailable: setting mBufferSlot %d", item.mBuf);
mBufferSlot[item.mBuf] = item.mGraphicBuffer;
}
mBufferQueue->releaseBuffer(item.mBuf, item.mFrameNumber,
@@ -782,6 +809,19 @@ void GraphicBufferSource::setSkipFramesBeforeUs(int64_t skipFramesBeforeUs) {
(skipFramesBeforeUs > 0) ? (skipFramesBeforeUs * 1000) : -1ll;
}
+status_t GraphicBufferSource::setTimeLapseUs(int64_t* data) {
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting || data[0] <= 0ll || data[1] <= 0ll) {
+ return INVALID_OPERATION;
+ }
+
+ mTimePerFrameUs = data[0];
+ mTimePerCaptureUs = data[1];
+
+ return OK;
+}
+
void GraphicBufferSource::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatRepeatLastFrame:
diff --git a/media/libstagefright/omx/GraphicBufferSource.h b/media/libstagefright/omx/GraphicBufferSource.h
index 153f2a0..fba42b7 100644
--- a/media/libstagefright/omx/GraphicBufferSource.h
+++ b/media/libstagefright/omx/GraphicBufferSource.h
@@ -118,6 +118,13 @@ public:
// of suspension on input.
status_t setMaxTimestampGapUs(int64_t maxGapUs);
+ // Sets the time lapse (or slow motion) parameters.
+ // data[0] is the time (us) between two frames for playback
+ // data[1] is the time (us) between two frames for capture
+ // When set, the sample's timestamp will be modified to playback framerate,
+ // and capture timestamp will be modified to capture rate.
+ status_t setTimeLapseUs(int64_t* data);
+
// Sets the start time us (in system time), samples before which should
// be dropped and not submitted to encoder
void setSkipFramesBeforeUs(int64_t startTimeUs);
@@ -250,6 +257,12 @@ private:
// no codec buffer was available at the time.
bool mRepeatBufferDeferred;
+ // Time lapse / slow motion configuration
+ int64_t mTimePerCaptureUs;
+ int64_t mTimePerFrameUs;
+ int64_t mPrevCaptureUs;
+ int64_t mPrevFrameUs;
+
void onMessageReceived(const sp<AMessage> &msg);
DISALLOW_EVIL_CONSTRUCTORS(GraphicBufferSource);
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index aa96389..0fb38fa 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -851,6 +851,7 @@ status_t OMXNodeInstance::setInternalOption(
case IOMX::INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY:
case IOMX::INTERNAL_OPTION_MAX_TIMESTAMP_GAP:
case IOMX::INTERNAL_OPTION_START_TIME:
+ case IOMX::INTERNAL_OPTION_TIME_LAPSE:
{
const sp<GraphicBufferSource> &bufferSource =
getGraphicBufferSource();
@@ -884,7 +885,7 @@ status_t OMXNodeInstance::setInternalOption(
int64_t maxGapUs = *(int64_t *)data;
return bufferSource->setMaxTimestampGapUs(maxGapUs);
- } else { // IOMX::INTERNAL_OPTION_START_TIME
+ } else if (type == IOMX::INTERNAL_OPTION_START_TIME) {
if (size != sizeof(int64_t)) {
return INVALID_OPERATION;
}
@@ -892,6 +893,12 @@ status_t OMXNodeInstance::setInternalOption(
int64_t skipFramesBeforeUs = *(int64_t *)data;
bufferSource->setSkipFramesBeforeUs(skipFramesBeforeUs);
+ } else { // IOMX::INTERNAL_OPTION_TIME_LAPSE
+ if (size != sizeof(int64_t) * 2) {
+ return INVALID_OPERATION;
+ }
+
+ bufferSource->setTimeLapseUs((int64_t *)data);
}
return OK;
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index b74fa89..7615086 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -183,6 +183,7 @@ AudioFlinger::AudioFlinger()
(void) property_get("af.tee", value, "0");
teeEnabled = atoi(value);
}
+ // FIXME symbolic constants here
if (teeEnabled & 1) {
mTeeSinkInputEnabled = true;
}
@@ -1810,7 +1811,7 @@ audio_io_handle_t AudioFlinger::openInput(audio_module_handle_t module,
kind = TEE_SINK_NEW;
} else if (mRecordTeeSink->getStrongCount() != 1) {
kind = TEE_SINK_NO;
- } else if (format == mRecordTeeSink->format()) {
+ } else if (Format_isEqual(format, mRecordTeeSink->format())) {
kind = TEE_SINK_OLD;
} else {
kind = TEE_SINK_NEW;
@@ -1847,8 +1848,6 @@ audio_io_handle_t AudioFlinger::openInput(audio_module_handle_t module,
// pre processing modules
RecordThread *thread = new RecordThread(this,
input,
- reqSamplingRate,
- reqChannelMask,
id,
primaryOutputDevice_l(),
*pDevices
@@ -2096,7 +2095,7 @@ sp<AudioFlinger::SyncEvent> AudioFlinger::createSyncEvent(AudioSystem::sync_even
int triggerSession,
int listenerSession,
sync_event_callback_t callBack,
- void *cookie)
+ wp<RefBase> cookie)
{
Mutex::Autolock _l(mLock);
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 4799beb..21d05d4 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -253,7 +253,7 @@ public:
int triggerSession,
int listenerSession,
sync_event_callback_t callBack,
- void *cookie)
+ wp<RefBase> cookie)
: mType(type), mTriggerSession(triggerSession), mListenerSession(listenerSession),
mCallback(callBack), mCookie(cookie)
{}
@@ -266,14 +266,14 @@ public:
AudioSystem::sync_event_t type() const { return mType; }
int triggerSession() const { return mTriggerSession; }
int listenerSession() const { return mListenerSession; }
- void *cookie() const { return mCookie; }
+ wp<RefBase> cookie() const { return mCookie; }
private:
const AudioSystem::sync_event_t mType;
const int mTriggerSession;
const int mListenerSession;
sync_event_callback_t mCallback;
- void * const mCookie;
+ const wp<RefBase> mCookie;
mutable Mutex mLock;
};
@@ -281,7 +281,7 @@ public:
int triggerSession,
int listenerSession,
sync_event_callback_t callBack,
- void *cookie);
+ wp<RefBase> cookie);
private:
class AudioHwDevice; // fwd declaration for findSuitableHwDev_l
@@ -638,7 +638,7 @@ public:
// 0x200000 stereo 16-bit PCM frames = 47.5 seconds at 44.1 kHz, 8 megabytes
static const size_t kTeeSinkInputFramesDefault = 0x200000;
static const size_t kTeeSinkOutputFramesDefault = 0x200000;
- static const size_t kTeeSinkTrackFramesDefault = 0x1000;
+ static const size_t kTeeSinkTrackFramesDefault = 0x200000;
#endif
// This method reads from a variable without mLock, but the variable is updated under mLock. So
diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp
index b206116..ca98f16 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -341,7 +341,7 @@ void AudioResamplerOrder1::resampleStereo16(int32_t* out, size_t outFrameCount,
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
- size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+ size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d",
// outFrameCount, inputIndex, phaseFraction, phaseIncrement);
@@ -439,7 +439,7 @@ void AudioResamplerOrder1::resampleMono16(int32_t* out, size_t outFrameCount,
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
- size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+ size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d",
// outFrameCount, inputIndex, phaseFraction, phaseIncrement);
diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h
index dc33f29..0592855 100644
--- a/services/audioflinger/AudioResampler.h
+++ b/services/audioflinger/AudioResampler.h
@@ -110,6 +110,38 @@ protected:
uint64_t mLocalTimeFreq;
int64_t mPTS;
+ // returns the inFrameCount required to generate outFrameCount frames.
+ //
+ // Placed here to be a consistent for all resamplers.
+ //
+ // Right now, we use the upper bound without regards to the current state of the
+ // input buffer using integer arithmetic, as follows:
+ //
+ // (static_cast<uint64_t>(outFrameCount)*mInSampleRate + (mSampleRate - 1))/mSampleRate;
+ //
+ // The double precision equivalent (float may not be precise enough):
+ // ceil(static_cast<double>(outFrameCount) * mInSampleRate / mSampleRate);
+ //
+ // this relies on the fact that the mPhaseIncrement is rounded down from
+ // #phases * mInSampleRate/mSampleRate and the fact that Sum(Floor(x)) <= Floor(Sum(x)).
+ // http://www.proofwiki.org/wiki/Sum_of_Floors_Not_Greater_Than_Floor_of_Sums
+ //
+ // (so long as double precision is computed accurately enough to be considered
+ // greater than or equal to the Floor(x) value in int32_t arithmetic; thus this
+ // will not necessarily hold for floats).
+ //
+ // TODO:
+ // Greater accuracy and a tight bound is obtained by:
+ // 1) subtract and adjust for the current state of the AudioBufferProvider buffer.
+ // 2) using the exact integer formula where (ignoring 64b casting)
+ // inFrameCount = (mPhaseIncrement * (outFrameCount - 1) + mPhaseFraction) / phaseWrapLimit;
+ // phaseWrapLimit is the wraparound (1 << kNumPhaseBits), if not specified explicitly.
+ //
+ inline size_t getInFrameCountRequired(size_t outFrameCount) {
+ return (static_cast<uint64_t>(outFrameCount)*mInSampleRate
+ + (mSampleRate - 1))/mSampleRate;
+ }
+
private:
const src_quality mQuality;
diff --git a/services/audioflinger/AudioResamplerCubic.cpp b/services/audioflinger/AudioResamplerCubic.cpp
index 1f9714b..8f14ff9 100644
--- a/services/audioflinger/AudioResamplerCubic.cpp
+++ b/services/audioflinger/AudioResamplerCubic.cpp
@@ -60,7 +60,7 @@ void AudioResamplerCubic::resampleStereo16(int32_t* out, size_t outFrameCount,
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
- size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+ size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// fetch first buffer
if (mBuffer.frameCount == 0) {
@@ -128,7 +128,7 @@ void AudioResamplerCubic::resampleMono16(int32_t* out, size_t outFrameCount,
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
- size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+ size_t inFrameCount = getInFrameCountRequired(outFrameCount);
// fetch first buffer
if (mBuffer.frameCount == 0) {
diff --git a/services/audioflinger/AudioResamplerDyn.cpp b/services/audioflinger/AudioResamplerDyn.cpp
index 2997c5c..7e4ca0c 100644
--- a/services/audioflinger/AudioResamplerDyn.cpp
+++ b/services/audioflinger/AudioResamplerDyn.cpp
@@ -470,7 +470,7 @@ void AudioResamplerDyn::resample(int32_t* out, size_t outFrameCount,
const uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2; // stereo output
- size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+ size_t inFrameCount = getInFrameCountRequired(outFrameCount);
const uint32_t phaseWrapLimit = c.mL << c.mShift;
// NOTE: be very careful when modifying the code here. register
diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp
index 207f26b..d0a7a58 100644
--- a/services/audioflinger/AudioResamplerSinc.cpp
+++ b/services/audioflinger/AudioResamplerSinc.cpp
@@ -540,7 +540,7 @@ void AudioResamplerSinc::resample(int32_t* out, size_t outFrameCount,
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
- size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate;
+ size_t inFrameCount = getInFrameCountRequired(outFrameCount);
while (outputIndex < outputSampleCount) {
// buffer is empty, fetch a new one
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index fc3171f..3ec9889 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -47,6 +47,9 @@ public:
static void appendDumpHeader(String8& result);
void dump(char* buffer, size_t size, bool active);
+ void handleSyncStartEvent(const sp<SyncEvent>& event);
+ void clearSyncStartEvent();
+
private:
friend class AudioFlinger; // for mState
@@ -59,4 +62,33 @@ private:
// releaseBuffer() not overridden
bool mOverflow; // overflow on most recent attempt to fill client buffer
+
+ // updated by RecordThread::readInputParameters_l()
+ AudioResampler *mResampler;
+
+ // interleaved stereo pairs of fixed-point signed Q19.12
+ int32_t *mRsmpOutBuffer;
+ // current allocated frame count for the above, which may be larger than needed
+ size_t mRsmpOutFrameCount;
+
+ size_t mRsmpInUnrel; // unreleased frames remaining from
+ // most recent getNextBuffer
+ // for debug only
+
+ // rolling counter that is never cleared
+ int32_t mRsmpInFront; // next available frame
+
+ AudioBufferProvider::Buffer mSink; // references client's buffer sink in shared memory
+
+ // sync event triggering actual audio capture. Frames read before this event will
+ // be dropped and therefore not read by the application.
+ sp<SyncEvent> mSyncStartEvent;
+
+ // number of captured frames to drop after the start sync event has been received.
+ // when < 0, maximum frames to drop before starting capture even if sync event is
+ // not received
+ ssize_t mFramesToDrop;
+
+ // used by resampler to find source frames
+ ResamplerBufferProvider *mResamplerBufferProvider;
};
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index b064e89..3e8c133 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -274,7 +274,8 @@ AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, audio
mType(type),
mAudioFlinger(audioFlinger),
// mSampleRate, mFrameCount, mChannelMask, mChannelCount, mFrameSize, mFormat, mBufferSize
- // are set by PlaybackThread::readOutputParameters() or RecordThread::readInputParameters()
+ // are set by PlaybackThread::readOutputParameters_l() or
+ // RecordThread::readInputParameters_l()
mParamStatus(NO_ERROR),
//FIXME: mStandby should be true here. Is this some kind of hack?
mStandby(false), mOutDevice(outDevice), mInDevice(inDevice),
@@ -1108,7 +1109,7 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge
}
}
- readOutputParameters();
+ readOutputParameters_l();
// mStreamTypes[AUDIO_STREAM_CNT] is initialized by stream_type_t default constructor
// There is no AUDIO_STREAM_MIN, and ++ operator does not compile
@@ -1677,7 +1678,7 @@ int AudioFlinger::PlaybackThread::asyncCallback(stream_callback_event_t event,
return 0;
}
-void AudioFlinger::PlaybackThread::readOutputParameters()
+void AudioFlinger::PlaybackThread::readOutputParameters_l()
{
// unfortunately we have no way of recovering from errors here, hence the LOG_FATAL
mSampleRate = mOutput->stream->common.get_sample_rate(&mOutput->stream->common);
@@ -1765,7 +1766,7 @@ void AudioFlinger::PlaybackThread::readOutputParameters()
// force reconfiguration of effect chains and engines to take new buffer size and audio
// parameters into account
- // Note that mLock is not held when readOutputParameters() is called from the constructor
+ // Note that mLock is not held when readOutputParameters_l() is called from the constructor
// but in this case nothing is done below as no audio sessions have effect yet so it doesn't
// matter.
// create a copy of mEffectChains as calling moveEffectChain_l() can reorder some effect chains
@@ -3485,7 +3486,7 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l()
keyValuePair.string());
}
if (status == NO_ERROR && reconfig) {
- readOutputParameters();
+ readOutputParameters_l();
delete mAudioMixer;
mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);
for (size_t i = 0; i < mTracks.size() ; i++) {
@@ -3827,7 +3828,7 @@ bool AudioFlinger::DirectOutputThread::checkForNewParameters_l()
keyValuePair.string());
}
if (status == NO_ERROR && reconfig) {
- readOutputParameters();
+ readOutputParameters_l();
sendIoConfigEvent_l(AudioSystem::OUTPUT_CONFIG_CHANGED);
}
}
@@ -4450,8 +4451,6 @@ void AudioFlinger::DuplicatingThread::cacheParameters_l()
AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
AudioStreamIn *input,
- uint32_t sampleRate,
- audio_channel_mask_t channelMask,
audio_io_handle_t id,
audio_devices_t outDevice,
audio_devices_t inDevice
@@ -4460,14 +4459,9 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
#endif
) :
ThreadBase(audioFlinger, id, outDevice, inDevice, RECORD),
- mInput(input), mActiveTracksGen(0), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpInBuffer(NULL),
- // mRsmpInFrames, mRsmpInFramesP2, mRsmpInUnrel, mRsmpInFront, and mRsmpInRear
- // are set by readInputParameters()
- // mRsmpInIndex LEGACY
- mReqChannelCount(popcount(channelMask)),
- mReqSampleRate(sampleRate)
- // mBytesRead is only meaningful while active, and so is cleared in start()
- // (but might be better to also clear here for dump?)
+ mInput(input), mActiveTracksGen(0), mRsmpInBuffer(NULL),
+ // mRsmpInFrames and mRsmpInFramesP2 are set by readInputParameters_l()
+ mRsmpInRear(0)
#ifdef TEE_SINK
, mTeeSink(teeSink)
#endif
@@ -4475,7 +4469,7 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
snprintf(mName, kNameLength, "AudioIn_%X", id);
mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mName);
- readInputParameters();
+ readInputParameters_l();
}
@@ -4483,8 +4477,6 @@ AudioFlinger::RecordThread::~RecordThread()
{
mAudioFlinger->unregisterWriter(mNBLogWriter);
delete[] mRsmpInBuffer;
- delete mResampler;
- delete[] mRsmpOutBuffer;
}
void AudioFlinger::RecordThread::onFirstRef()
@@ -4498,12 +4490,6 @@ bool AudioFlinger::RecordThread::threadLoop()
inputStandBy();
- // used to verify we've read at least once before evaluating how many bytes were read
- bool readOnce = false;
-
- // used to request a deferred sleep, to be executed later while mutex is unlocked
- bool doSleep = false;
-
reacquire_wakelock:
sp<RecordTrack> activeTrack;
int activeTracksGen;
@@ -4527,17 +4513,22 @@ reacquire_wakelock:
}
}
- // start recording
+ // used to request a deferred sleep, to be executed later while mutex is unlocked
+ uint32_t sleepUs = 0;
+
+ // loop while there is work to do
for (;;) {
- TrackBase::track_state activeTrackState;
Vector< sp<EffectChain> > effectChains;
// sleep with mutex unlocked
- if (doSleep) {
- doSleep = false;
- usleep(kRecordThreadSleepUs);
+ if (sleepUs > 0) {
+ usleep(sleepUs);
+ sleepUs = 0;
}
+ // activeTracks accumulates a copy of a subset of mActiveTracks
+ Vector< sp<RecordTrack> > activeTracks;
+
{ // scope for mLock
Mutex::Autolock _l(mLock);
@@ -4571,236 +4562,307 @@ reacquire_wakelock:
tmp.add(mActiveTracks[i]->uid());
}
updateWakeLockUids_l(tmp);
- // FIXME an arbitrary choice
- activeTrack = mActiveTracks[0];
- }
-
- if (activeTrack->isTerminated()) {
- removeTrack_l(activeTrack);
- mActiveTracks.remove(activeTrack);
- mActiveTracksGen++;
- continue;
}
- activeTrackState = activeTrack->mState;
- switch (activeTrackState) {
- case TrackBase::PAUSING:
- standbyIfNotAlreadyInStandby();
- mActiveTracks.remove(activeTrack);
- mActiveTracksGen++;
- mStartStopCond.broadcast();
- doSleep = true;
- continue;
+ bool doBroadcast = false;
+ for (size_t i = 0; i < size; ) {
- case TrackBase::RESUMING:
- mStandby = false;
- if (mReqChannelCount != activeTrack->channelCount()) {
+ activeTrack = mActiveTracks[i];
+ if (activeTrack->isTerminated()) {
+ removeTrack_l(activeTrack);
mActiveTracks.remove(activeTrack);
mActiveTracksGen++;
- mStartStopCond.broadcast();
+ size--;
continue;
}
- if (readOnce) {
- mStartStopCond.broadcast();
- // record start succeeds only if first read from audio input succeeds
- if (mBytesRead < 0) {
- mActiveTracks.remove(activeTrack);
- mActiveTracksGen++;
- continue;
- }
+
+ TrackBase::track_state activeTrackState = activeTrack->mState;
+ switch (activeTrackState) {
+
+ case TrackBase::PAUSING:
+ mActiveTracks.remove(activeTrack);
+ mActiveTracksGen++;
+ doBroadcast = true;
+ size--;
+ continue;
+
+ case TrackBase::STARTING_1:
+ sleepUs = 10000;
+ i++;
+ continue;
+
+ case TrackBase::STARTING_2:
+ doBroadcast = true;
+ mStandby = false;
activeTrack->mState = TrackBase::ACTIVE;
+ break;
+
+ case TrackBase::ACTIVE:
+ break;
+
+ case TrackBase::IDLE:
+ i++;
+ continue;
+
+ default:
+ LOG_FATAL("Unexpected activeTrackState %d", activeTrackState);
}
- break;
- case TrackBase::ACTIVE:
- break;
+ activeTracks.add(activeTrack);
+ i++;
- case TrackBase::IDLE:
- doSleep = true;
- continue;
+ }
+ if (doBroadcast) {
+ mStartStopCond.broadcast();
+ }
- default:
- LOG_FATAL("Unexpected activeTrackState %d", activeTrackState);
+ // sleep if there are no active tracks to process
+ if (activeTracks.size() == 0) {
+ if (sleepUs == 0) {
+ sleepUs = kRecordThreadSleepUs;
+ }
+ continue;
}
+ sleepUs = 0;
lockEffectChains_l(effectChains);
}
- // thread mutex is now unlocked, mActiveTracks unknown, activeTrack != 0, kept, immutable
- // activeTrack->mState unknown, activeTrackState immutable and is ACTIVE or RESUMING
+ // thread mutex is now unlocked, mActiveTracks unknown, activeTracks.size() > 0
- for (size_t i = 0; i < effectChains.size(); i ++) {
+ size_t size = effectChains.size();
+ for (size_t i = 0; i < size; i++) {
// thread mutex is not locked, but effect chain is locked
effectChains[i]->process_l();
}
- AudioBufferProvider::Buffer buffer;
- buffer.frameCount = mFrameCount;
- status_t status = activeTrack->getNextBuffer(&buffer);
- if (status == NO_ERROR) {
- readOnce = true;
- size_t framesOut = buffer.frameCount;
- if (mResampler == NULL) {
- // no resampling
- while (framesOut) {
- size_t framesIn = mFrameCount - mRsmpInIndex;
- if (framesIn > 0) {
- int8_t *src = (int8_t *)mRsmpInBuffer + mRsmpInIndex * mFrameSize;
- int8_t *dst = buffer.i8 + (buffer.frameCount - framesOut) *
- activeTrack->mFrameSize;
- if (framesIn > framesOut) {
- framesIn = framesOut;
+ // Read from HAL to keep up with fastest client if multiple active tracks, not slowest one.
+ // Only the client(s) that are too slow will overrun. But if even the fastest client is too
+ // slow, then this RecordThread will overrun by not calling HAL read often enough.
+ // If destination is non-contiguous, first read past the nominal end of buffer, then
+ // copy to the right place. Permitted because mRsmpInBuffer was over-allocated.
+
+ int32_t rear = mRsmpInRear & (mRsmpInFramesP2 - 1);
+ ssize_t bytesRead = mInput->stream->read(mInput->stream,
+ &mRsmpInBuffer[rear * mChannelCount], mBufferSize);
+ if (bytesRead <= 0) {
+ ALOGE("read failed: bytesRead=%d < %u", bytesRead, mBufferSize);
+ // Force input into standby so that it tries to recover at next read attempt
+ inputStandBy();
+ sleepUs = kRecordThreadSleepUs;
+ continue;
+ }
+ ALOG_ASSERT((size_t) bytesRead <= mBufferSize);
+ size_t framesRead = bytesRead / mFrameSize;
+ ALOG_ASSERT(framesRead > 0);
+ if (mTeeSink != 0) {
+ (void) mTeeSink->write(&mRsmpInBuffer[rear * mChannelCount], framesRead);
+ }
+ // If destination is non-contiguous, we now correct for reading past end of buffer.
+ size_t part1 = mRsmpInFramesP2 - rear;
+ if (framesRead > part1) {
+ memcpy(mRsmpInBuffer, &mRsmpInBuffer[mRsmpInFramesP2 * mChannelCount],
+ (framesRead - part1) * mFrameSize);
+ }
+ rear = mRsmpInRear += framesRead;
+
+ size = activeTracks.size();
+ // loop over each active track
+ for (size_t i = 0; i < size; i++) {
+ activeTrack = activeTracks[i];
+
+ enum {
+ OVERRUN_UNKNOWN,
+ OVERRUN_TRUE,
+ OVERRUN_FALSE
+ } overrun = OVERRUN_UNKNOWN;
+
+ // loop over getNextBuffer to handle circular sink
+ for (;;) {
+
+ activeTrack->mSink.frameCount = ~0;
+ status_t status = activeTrack->getNextBuffer(&activeTrack->mSink);
+ size_t framesOut = activeTrack->mSink.frameCount;
+ LOG_ALWAYS_FATAL_IF((status == OK) != (framesOut > 0));
+
+ int32_t front = activeTrack->mRsmpInFront;
+ ssize_t filled = rear - front;
+ size_t framesIn;
+
+ if (filled < 0) {
+ // should not happen, but treat like a massive overrun and re-sync
+ framesIn = 0;
+ activeTrack->mRsmpInFront = rear;
+ overrun = OVERRUN_TRUE;
+ } else if ((size_t) filled <= mRsmpInFrames) {
+ framesIn = (size_t) filled;
+ } else {
+ // client is not keeping up with server, but give it latest data
+ framesIn = mRsmpInFrames;
+ activeTrack->mRsmpInFront = front = rear - framesIn;
+ overrun = OVERRUN_TRUE;
+ }
+
+ if (framesOut == 0 || framesIn == 0) {
+ break;
+ }
+
+ if (activeTrack->mResampler == NULL) {
+ // no resampling
+ if (framesIn > framesOut) {
+ framesIn = framesOut;
+ } else {
+ framesOut = framesIn;
+ }
+ int8_t *dst = activeTrack->mSink.i8;
+ while (framesIn > 0) {
+ front &= mRsmpInFramesP2 - 1;
+ size_t part1 = mRsmpInFramesP2 - front;
+ if (part1 > framesIn) {
+ part1 = framesIn;
}
- mRsmpInIndex += framesIn;
- framesOut -= framesIn;
- if (mChannelCount == mReqChannelCount) {
- memcpy(dst, src, framesIn * mFrameSize);
+ int8_t *src = (int8_t *)mRsmpInBuffer + (front * mFrameSize);
+ if (mChannelCount == activeTrack->mChannelCount) {
+ memcpy(dst, src, part1 * mFrameSize);
+ } else if (mChannelCount == 1) {
+ upmix_to_stereo_i16_from_mono_i16((int16_t *)dst, (int16_t *)src,
+ part1);
} else {
- if (mChannelCount == 1) {
- upmix_to_stereo_i16_from_mono_i16((int16_t *)dst,
- (int16_t *)src, framesIn);
- } else {
- downmix_to_mono_i16_from_stereo_i16((int16_t *)dst,
- (int16_t *)src, framesIn);
- }
+ downmix_to_mono_i16_from_stereo_i16((int16_t *)dst, (int16_t *)src,
+ part1);
}
+ dst += part1 * activeTrack->mFrameSize;
+ front += part1;
+ framesIn -= part1;
}
- if (framesOut > 0 && mFrameCount == mRsmpInIndex) {
- void *readInto;
- if (framesOut == mFrameCount && mChannelCount == mReqChannelCount) {
- readInto = buffer.raw;
- framesOut = 0;
- } else {
- readInto = mRsmpInBuffer;
- mRsmpInIndex = 0;
- }
- mBytesRead = mInput->stream->read(mInput->stream, readInto, mBufferSize);
- if (mBytesRead <= 0) {
- // TODO: verify that it's benign to use a stale track state
- if ((mBytesRead < 0) && (activeTrackState == TrackBase::ACTIVE))
- {
- ALOGE("Error reading audio input");
- // Force input into standby so that it tries to
- // recover at next read attempt
- inputStandBy();
- doSleep = true;
- }
- mRsmpInIndex = mFrameCount;
- framesOut = 0;
- buffer.frameCount = 0;
- }
-#ifdef TEE_SINK
- else if (mTeeSink != 0) {
- (void) mTeeSink->write(readInto,
- mBytesRead >> Format_frameBitShift(mTeeSink->format()));
+ activeTrack->mRsmpInFront += framesOut;
+
+ } else {
+ // resampling
+ // FIXME framesInNeeded should really be part of resampler API, and should
+ // depend on the SRC ratio
+ // to keep mRsmpInBuffer full so resampler always has sufficient input
+ size_t framesInNeeded;
+ // FIXME only re-calculate when it changes, and optimize for common ratios
+ double inOverOut = (double) mSampleRate / activeTrack->mSampleRate;
+ double outOverIn = (double) activeTrack->mSampleRate / mSampleRate;
+ framesInNeeded = ceil(framesOut * inOverOut) + 1;
+ ALOGV("need %u frames in to produce %u out given in/out ratio of %.4g",
+ framesInNeeded, framesOut, inOverOut);
+ // Although we theoretically have framesIn in circular buffer, some of those are
+ // unreleased frames, and thus must be discounted for purpose of budgeting.
+ size_t unreleased = activeTrack->mRsmpInUnrel;
+ framesIn = framesIn > unreleased ? framesIn - unreleased : 0;
+ if (framesIn < framesInNeeded) {
+ ALOGV("not enough to resample: have %u frames in but need %u in to "
+ "produce %u out given in/out ratio of %.4g",
+ framesIn, framesInNeeded, framesOut, inOverOut);
+ size_t newFramesOut = framesIn > 0 ? floor((framesIn - 1) * outOverIn) : 0;
+ LOG_ALWAYS_FATAL_IF(newFramesOut >= framesOut);
+ if (newFramesOut == 0) {
+ break;
}
-#endif
- }
- }
- } else {
- // resampling
-
- // avoid busy-waiting if client doesn't keep up
- bool madeProgress = false;
-
- // keep mRsmpInBuffer full so resampler always has sufficient input
- for (;;) {
- int32_t rear = mRsmpInRear;
- ssize_t filled = rear - mRsmpInFront;
- ALOG_ASSERT(0 <= filled && (size_t) filled <= mRsmpInFramesP2);
- // exit once there is enough data in buffer for resampler
- if ((size_t) filled >= mRsmpInFrames) {
- break;
- }
- size_t avail = mRsmpInFramesP2 - filled;
- // Only try to read full HAL buffers.
- // But if the HAL read returns a partial buffer, use it.
- if (avail < mFrameCount) {
- ALOGE("insufficient space to read: avail %d < mFrameCount %d",
- avail, mFrameCount);
- break;
+ framesInNeeded = ceil(newFramesOut * inOverOut) + 1;
+ ALOGV("now need %u frames in to produce %u out given out/in ratio of %.4g",
+ framesInNeeded, newFramesOut, outOverIn);
+ LOG_ALWAYS_FATAL_IF(framesIn < framesInNeeded);
+ ALOGV("success 2: have %u frames in and need %u in to produce %u out "
+ "given in/out ratio of %.4g",
+ framesIn, framesInNeeded, newFramesOut, inOverOut);
+ framesOut = newFramesOut;
+ } else {
+ ALOGV("success 1: have %u in and need %u in to produce %u out "
+ "given in/out ratio of %.4g",
+ framesIn, framesInNeeded, framesOut, inOverOut);
}
- // If 'avail' is non-contiguous, first read past the nominal end of buffer, then
- // copy to the right place. Permitted because mRsmpInBuffer was over-allocated.
- rear &= mRsmpInFramesP2 - 1;
- mBytesRead = mInput->stream->read(mInput->stream,
- &mRsmpInBuffer[rear * mChannelCount], mBufferSize);
- if (mBytesRead <= 0) {
- ALOGE("read failed: mBytesRead=%d < %u", mBytesRead, mBufferSize);
- break;
+
+ // reallocate mRsmpOutBuffer as needed; we will grow but never shrink
+ if (activeTrack->mRsmpOutFrameCount < framesOut) {
+ // FIXME why does each track need it's own mRsmpOutBuffer? can't they share?
+ delete[] activeTrack->mRsmpOutBuffer;
+ // resampler always outputs stereo
+ activeTrack->mRsmpOutBuffer = new int32_t[framesOut * FCC_2];
+ activeTrack->mRsmpOutFrameCount = framesOut;
}
- ALOG_ASSERT((size_t) mBytesRead <= mBufferSize);
- size_t framesRead = mBytesRead / mFrameSize;
- ALOG_ASSERT(framesRead > 0);
- madeProgress = true;
- // If 'avail' was non-contiguous, we now correct for reading past end of buffer.
- size_t part1 = mRsmpInFramesP2 - rear;
- if (framesRead > part1) {
- memcpy(mRsmpInBuffer, &mRsmpInBuffer[mRsmpInFramesP2 * mChannelCount],
- (framesRead - part1) * mFrameSize);
+
+ // resampler accumulates, but we only have one source track
+ memset(activeTrack->mRsmpOutBuffer, 0, framesOut * FCC_2 * sizeof(int32_t));
+ activeTrack->mResampler->resample(activeTrack->mRsmpOutBuffer, framesOut,
+ // FIXME how about having activeTrack implement this interface itself?
+ activeTrack->mResamplerBufferProvider
+ /*this*/ /* AudioBufferProvider* */);
+ // ditherAndClamp() works as long as all buffers returned by
+ // activeTrack->getNextBuffer() are 32 bit aligned which should be always true.
+ if (activeTrack->mChannelCount == 1) {
+ // temporarily type pun mRsmpOutBuffer from Q19.12 to int16_t
+ ditherAndClamp(activeTrack->mRsmpOutBuffer, activeTrack->mRsmpOutBuffer,
+ framesOut);
+ // the resampler always outputs stereo samples:
+ // do post stereo to mono conversion
+ downmix_to_mono_i16_from_stereo_i16(activeTrack->mSink.i16,
+ (int16_t *)activeTrack->mRsmpOutBuffer, framesOut);
+ } else {
+ ditherAndClamp((int32_t *)activeTrack->mSink.raw,
+ activeTrack->mRsmpOutBuffer, framesOut);
}
- mRsmpInRear += framesRead;
- }
+ // now done with mRsmpOutBuffer
- if (!madeProgress) {
- ALOGV("Did not make progress");
- usleep(((mFrameCount * 1000) / mSampleRate) * 1000);
}
- // resampler accumulates, but we only have one source track
- memset(mRsmpOutBuffer, 0, framesOut * FCC_2 * sizeof(int32_t));
- mResampler->resample(mRsmpOutBuffer, framesOut,
- this /* AudioBufferProvider* */);
- // ditherAndClamp() works as long as all buffers returned by
- // activeTrack->getNextBuffer() are 32 bit aligned which should be always true.
- if (mReqChannelCount == 1) {
- // temporarily type pun mRsmpOutBuffer from Q19.12 to int16_t
- ditherAndClamp(mRsmpOutBuffer, mRsmpOutBuffer, framesOut);
- // the resampler always outputs stereo samples:
- // do post stereo to mono conversion
- downmix_to_mono_i16_from_stereo_i16(buffer.i16, (int16_t *)mRsmpOutBuffer,
- framesOut);
- } else {
- ditherAndClamp((int32_t *)buffer.raw, mRsmpOutBuffer, framesOut);
+ if (framesOut > 0 && (overrun == OVERRUN_UNKNOWN)) {
+ overrun = OVERRUN_FALSE;
}
- // now done with mRsmpOutBuffer
- }
- if (mFramestoDrop == 0) {
- activeTrack->releaseBuffer(&buffer);
- } else {
- if (mFramestoDrop > 0) {
- mFramestoDrop -= buffer.frameCount;
- if (mFramestoDrop <= 0) {
- clearSyncStartEvent();
+ if (activeTrack->mFramesToDrop == 0) {
+ if (framesOut > 0) {
+ activeTrack->mSink.frameCount = framesOut;
+ activeTrack->releaseBuffer(&activeTrack->mSink);
}
} else {
- mFramestoDrop += buffer.frameCount;
- if (mFramestoDrop >= 0 || mSyncStartEvent == 0 ||
- mSyncStartEvent->isCancelled()) {
- ALOGW("Synced record %s, session %d, trigger session %d",
- (mFramestoDrop >= 0) ? "timed out" : "cancelled",
- activeTrack->sessionId(),
- (mSyncStartEvent != 0) ? mSyncStartEvent->triggerSession() : 0);
- clearSyncStartEvent();
+ // FIXME could do a partial drop of framesOut
+ if (activeTrack->mFramesToDrop > 0) {
+ activeTrack->mFramesToDrop -= framesOut;
+ if (activeTrack->mFramesToDrop <= 0) {
+ activeTrack->clearSyncStartEvent();
+ }
+ } else {
+ activeTrack->mFramesToDrop += framesOut;
+ if (activeTrack->mFramesToDrop >= 0 || activeTrack->mSyncStartEvent == 0 ||
+ activeTrack->mSyncStartEvent->isCancelled()) {
+ ALOGW("Synced record %s, session %d, trigger session %d",
+ (activeTrack->mFramesToDrop >= 0) ? "timed out" : "cancelled",
+ activeTrack->sessionId(),
+ (activeTrack->mSyncStartEvent != 0) ?
+ activeTrack->mSyncStartEvent->triggerSession() : 0);
+ activeTrack->clearSyncStartEvent();
+ }
}
}
+
+ if (framesOut == 0) {
+ break;
+ }
}
- activeTrack->clearOverflow();
- }
- // client isn't retrieving buffers fast enough
- else {
- if (!activeTrack->setOverflow()) {
- nsecs_t now = systemTime();
- if ((now - lastWarning) > kWarningThrottleNs) {
- ALOGW("RecordThread: buffer overflow");
- lastWarning = now;
+
+ switch (overrun) {
+ case OVERRUN_TRUE:
+ // client isn't retrieving buffers fast enough
+ if (!activeTrack->setOverflow()) {
+ nsecs_t now = systemTime();
+ // FIXME should lastWarning per track?
+ if ((now - lastWarning) > kWarningThrottleNs) {
+ ALOGW("RecordThread: buffer overflow");
+ lastWarning = now;
+ }
}
+ break;
+ case OVERRUN_FALSE:
+ activeTrack->clearOverflow();
+ break;
+ case OVERRUN_UNKNOWN:
+ break;
}
- // Release the processor for a while before asking for a new buffer.
- // This will give the application more chance to read from the buffer and
- // clear the overflow.
- doSleep = true;
+
}
// enable changes in effect chain
@@ -4959,114 +5021,92 @@ status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrac
status_t status = NO_ERROR;
if (event == AudioSystem::SYNC_EVENT_NONE) {
- clearSyncStartEvent();
+ recordTrack->clearSyncStartEvent();
} else if (event != AudioSystem::SYNC_EVENT_SAME) {
- mSyncStartEvent = mAudioFlinger->createSyncEvent(event,
+ recordTrack->mSyncStartEvent = mAudioFlinger->createSyncEvent(event,
triggerSession,
recordTrack->sessionId(),
syncStartEventCallback,
- this);
+ recordTrack);
// Sync event can be cancelled by the trigger session if the track is not in a
// compatible state in which case we start record immediately
- if (mSyncStartEvent->isCancelled()) {
- clearSyncStartEvent();
+ if (recordTrack->mSyncStartEvent->isCancelled()) {
+ recordTrack->clearSyncStartEvent();
} else {
// do not wait for the event for more than AudioSystem::kSyncRecordStartTimeOutMs
- mFramestoDrop = - ((AudioSystem::kSyncRecordStartTimeOutMs * mReqSampleRate) / 1000);
+ recordTrack->mFramesToDrop = -
+ ((AudioSystem::kSyncRecordStartTimeOutMs * recordTrack->mSampleRate) / 1000);
}
}
{
// This section is a rendezvous between binder thread executing start() and RecordThread
AutoMutex lock(mLock);
- if (mActiveTracks.size() > 0) {
- // FIXME does not work for multiple active tracks
- if (mActiveTracks.indexOf(recordTrack) != 0) {
- status = -EBUSY;
- } else if (recordTrack->mState == TrackBase::PAUSING) {
+ if (mActiveTracks.indexOf(recordTrack) >= 0) {
+ if (recordTrack->mState == TrackBase::PAUSING) {
+ ALOGV("active record track PAUSING -> ACTIVE");
recordTrack->mState = TrackBase::ACTIVE;
+ } else {
+ ALOGV("active record track state %d", recordTrack->mState);
}
return status;
}
- // FIXME why? already set in constructor, 'STARTING_1' would be more accurate
- recordTrack->mState = TrackBase::IDLE;
+ // TODO consider other ways of handling this, such as changing the state to :STARTING and
+ // adding the track to mActiveTracks after returning from AudioSystem::startInput(),
+ // or using a separate command thread
+ recordTrack->mState = TrackBase::STARTING_1;
mActiveTracks.add(recordTrack);
mActiveTracksGen++;
mLock.unlock();
status_t status = AudioSystem::startInput(mId);
mLock.lock();
- // FIXME should verify that mActiveTrack is still == recordTrack
+ // FIXME should verify that recordTrack is still in mActiveTracks
if (status != NO_ERROR) {
mActiveTracks.remove(recordTrack);
mActiveTracksGen++;
- clearSyncStartEvent();
+ recordTrack->clearSyncStartEvent();
return status;
}
- // FIXME LEGACY
- mRsmpInIndex = mFrameCount;
- mRsmpInFront = 0;
- mRsmpInRear = 0;
- mRsmpInUnrel = 0;
- mBytesRead = 0;
- if (mResampler != NULL) {
- mResampler->reset();
- }
- // FIXME hijacking a playback track state name which was intended for start after pause;
- // here 'STARTING_2' would be more accurate
- recordTrack->mState = TrackBase::RESUMING;
+ // Catch up with current buffer indices if thread is already running.
+ // This is what makes a new client discard all buffered data. If the track's mRsmpInFront
+ // was initialized to some value closer to the thread's mRsmpInFront, then the track could
+ // see previously buffered data before it called start(), but with greater risk of overrun.
+
+ recordTrack->mRsmpInFront = mRsmpInRear;
+ recordTrack->mRsmpInUnrel = 0;
+ // FIXME why reset?
+ if (recordTrack->mResampler != NULL) {
+ recordTrack->mResampler->reset();
+ }
+ recordTrack->mState = TrackBase::STARTING_2;
// signal thread to start
- ALOGV("Signal record thread");
mWaitWorkCV.broadcast();
- // do not wait for mStartStopCond if exiting
- if (exitPending()) {
- mActiveTracks.remove(recordTrack);
- mActiveTracksGen++;
- status = INVALID_OPERATION;
- goto startError;
- }
- // FIXME incorrect usage of wait: no explicit predicate or loop
- mStartStopCond.wait(mLock);
if (mActiveTracks.indexOf(recordTrack) < 0) {
ALOGV("Record failed to start");
status = BAD_VALUE;
goto startError;
}
- ALOGV("Record started OK");
return status;
}
startError:
AudioSystem::stopInput(mId);
- clearSyncStartEvent();
+ recordTrack->clearSyncStartEvent();
+ // FIXME I wonder why we do not reset the state here?
return status;
}
-void AudioFlinger::RecordThread::clearSyncStartEvent()
-{
- if (mSyncStartEvent != 0) {
- mSyncStartEvent->cancel();
- }
- mSyncStartEvent.clear();
- mFramestoDrop = 0;
-}
-
void AudioFlinger::RecordThread::syncStartEventCallback(const wp<SyncEvent>& event)
{
sp<SyncEvent> strongEvent = event.promote();
if (strongEvent != 0) {
- RecordThread *me = (RecordThread *)strongEvent->cookie();
- me->handleSyncStartEvent(strongEvent);
- }
-}
-
-void AudioFlinger::RecordThread::handleSyncStartEvent(const sp<SyncEvent>& event)
-{
- if (event == mSyncStartEvent) {
- // TODO: use actual buffer filling status instead of 2 buffers when info is available
- // from audio HAL
- mFramestoDrop = mFrameCount * 2;
+ sp<RefBase> ptr = strongEvent->cookie().promote();
+ if (ptr != 0) {
+ RecordTrack *recordTrack = (RecordTrack *)ptr.get();
+ recordTrack->handleSyncStartEvent(strongEvent);
+ }
}
}
@@ -5151,13 +5191,9 @@ void AudioFlinger::RecordThread::dumpInternals(int fd, const Vector<String16>& a
fdprintf(fd, "\nInput thread %p:\n", this);
if (mActiveTracks.size() > 0) {
- fdprintf(fd, " In index: %zu\n", mRsmpInIndex);
fdprintf(fd, " Buffer size: %zu bytes\n", mBufferSize);
- fdprintf(fd, " Resampling: %d\n", (mResampler != NULL));
- fdprintf(fd, " Out channel count: %u\n", mReqChannelCount);
- fdprintf(fd, " Out sample rate: %u\n", mReqSampleRate);
} else {
- fdprintf(fd, " No active record client\n");
+ fdprintf(fd, " No active record clients\n");
}
dumpBase(fd, args);
@@ -5209,15 +5245,26 @@ void AudioFlinger::RecordThread::dumpTracks(int fd, const Vector<String16>& args
}
// AudioBufferProvider interface
-status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts __unused)
+status_t AudioFlinger::RecordThread::ResamplerBufferProvider::getNextBuffer(
+ AudioBufferProvider::Buffer* buffer, int64_t pts __unused)
{
- int32_t rear = mRsmpInRear;
- int32_t front = mRsmpInFront;
+ RecordTrack *activeTrack = mRecordTrack;
+ sp<ThreadBase> threadBase = activeTrack->mThread.promote();
+ if (threadBase == 0) {
+ buffer->frameCount = 0;
+ buffer->raw = NULL;
+ return NOT_ENOUGH_DATA;
+ }
+ RecordThread *recordThread = (RecordThread *) threadBase.get();
+ int32_t rear = recordThread->mRsmpInRear;
+ int32_t front = activeTrack->mRsmpInFront;
ssize_t filled = rear - front;
- ALOG_ASSERT(0 <= filled && (size_t) filled <= mRsmpInFramesP2);
+ // FIXME should not be P2 (don't want to increase latency)
+ // FIXME if client not keeping up, discard
+ LOG_ALWAYS_FATAL_IF(!(0 <= filled && (size_t) filled <= recordThread->mRsmpInFrames));
// 'filled' may be non-contiguous, so return only the first contiguous chunk
- front &= mRsmpInFramesP2 - 1;
- size_t part1 = mRsmpInFramesP2 - front;
+ front &= recordThread->mRsmpInFramesP2 - 1;
+ size_t part1 = recordThread->mRsmpInFramesP2 - front;
if (part1 > (size_t) filled) {
part1 = filled;
}
@@ -5228,29 +5275,31 @@ status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer*
}
if (part1 == 0) {
// Higher-level should keep mRsmpInBuffer full, and not call resampler if empty
- ALOGE("RecordThread::getNextBuffer() starved");
+ LOG_ALWAYS_FATAL("RecordThread::getNextBuffer() starved");
buffer->raw = NULL;
buffer->frameCount = 0;
- mRsmpInUnrel = 0;
+ activeTrack->mRsmpInUnrel = 0;
return NOT_ENOUGH_DATA;
}
- buffer->raw = mRsmpInBuffer + front * mChannelCount;
+ buffer->raw = recordThread->mRsmpInBuffer + front * recordThread->mChannelCount;
buffer->frameCount = part1;
- mRsmpInUnrel = part1;
+ activeTrack->mRsmpInUnrel = part1;
return NO_ERROR;
}
// AudioBufferProvider interface
-void AudioFlinger::RecordThread::releaseBuffer(AudioBufferProvider::Buffer* buffer)
+void AudioFlinger::RecordThread::ResamplerBufferProvider::releaseBuffer(
+ AudioBufferProvider::Buffer* buffer)
{
+ RecordTrack *activeTrack = mRecordTrack;
size_t stepCount = buffer->frameCount;
if (stepCount == 0) {
return;
}
- ALOG_ASSERT(stepCount <= mRsmpInUnrel);
- mRsmpInUnrel -= stepCount;
- mRsmpInFront += stepCount;
+ ALOG_ASSERT(stepCount <= activeTrack->mRsmpInUnrel);
+ activeTrack->mRsmpInUnrel -= stepCount;
+ activeTrack->mRsmpInFront += stepCount;
buffer->raw = NULL;
buffer->frameCount = 0;
}
@@ -5265,11 +5314,14 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l()
AudioParameter param = AudioParameter(keyValuePair);
int value;
audio_format_t reqFormat = mFormat;
- uint32_t reqSamplingRate = mReqSampleRate;
- audio_channel_mask_t reqChannelMask = audio_channel_in_mask_from_count(mReqChannelCount);
+ uint32_t samplingRate = mSampleRate;
+ audio_channel_mask_t channelMask = audio_channel_in_mask_from_count(mChannelCount);
+ // TODO Investigate when this code runs. Check with audio policy when a sample rate and
+ // channel count change can be requested. Do we mandate the first client defines the
+ // HAL sampling rate and channel count or do we allow changes on the fly?
if (param.getInt(String8(AudioParameter::keySamplingRate), value) == NO_ERROR) {
- reqSamplingRate = value;
+ samplingRate = value;
reconfig = true;
}
if (param.getInt(String8(AudioParameter::keyFormat), value) == NO_ERROR) {
@@ -5285,7 +5337,7 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l()
if (mask != AUDIO_CHANNEL_IN_MONO && mask != AUDIO_CHANNEL_IN_STEREO) {
status = BAD_VALUE;
} else {
- reqChannelMask = mask;
+ channelMask = mask;
reconfig = true;
}
}
@@ -5350,15 +5402,15 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l()
reqFormat == mInput->stream->common.get_format(&mInput->stream->common) &&
reqFormat == AUDIO_FORMAT_PCM_16_BIT &&
(mInput->stream->common.get_sample_rate(&mInput->stream->common)
- <= (2 * reqSamplingRate)) &&
+ <= (2 * samplingRate)) &&
popcount(mInput->stream->common.get_channels(&mInput->stream->common))
<= FCC_2 &&
- (reqChannelMask == AUDIO_CHANNEL_IN_MONO ||
- reqChannelMask == AUDIO_CHANNEL_IN_STEREO)) {
+ (channelMask == AUDIO_CHANNEL_IN_MONO ||
+ channelMask == AUDIO_CHANNEL_IN_STEREO)) {
status = NO_ERROR;
}
if (status == NO_ERROR) {
- readInputParameters();
+ readInputParameters_l();
sendIoConfigEvent_l(AudioSystem::INPUT_CONFIG_CHANGED);
}
}
@@ -5410,15 +5462,8 @@ void AudioFlinger::RecordThread::audioConfigChanged_l(int event, int param __unu
mAudioFlinger->audioConfigChanged_l(event, mId, param2);
}
-void AudioFlinger::RecordThread::readInputParameters()
+void AudioFlinger::RecordThread::readInputParameters_l()
{
- delete[] mRsmpInBuffer;
- // mRsmpInBuffer is always assigned a new[] below
- delete[] mRsmpOutBuffer;
- mRsmpOutBuffer = NULL;
- delete mResampler;
- mResampler = NULL;
-
mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common);
mChannelMask = mInput->stream->common.get_channels(&mInput->stream->common);
mChannelCount = popcount(mChannelMask);
@@ -5429,24 +5474,20 @@ void AudioFlinger::RecordThread::readInputParameters()
mFrameSize = audio_stream_frame_size(&mInput->stream->common);
mBufferSize = mInput->stream->common.get_buffer_size(&mInput->stream->common);
mFrameCount = mBufferSize / mFrameSize;
+ // This is the formula for calculating the temporary buffer size.
// With 3 HAL buffers, we can guarantee ability to down-sample the input by ratio of 2:1 to
// 1 full output buffer, regardless of the alignment of the available input.
+ // The "3" is somewhat arbitrary, and could probably be larger.
+ // A larger value should allow more old data to be read after a track calls start(),
+ // without increasing latency.
mRsmpInFrames = mFrameCount * 3;
mRsmpInFramesP2 = roundup(mRsmpInFrames);
+ delete[] mRsmpInBuffer;
// Over-allocate beyond mRsmpInFramesP2 to permit a HAL read past end of buffer
mRsmpInBuffer = new int16_t[(mRsmpInFramesP2 + mFrameCount - 1) * mChannelCount];
- mRsmpInFront = 0;
- mRsmpInRear = 0;
- mRsmpInUnrel = 0;
-
- if (mSampleRate != mReqSampleRate && mChannelCount <= FCC_2 && mReqChannelCount <= FCC_2) {
- mResampler = AudioResampler::create(16, (int) mChannelCount, mReqSampleRate);
- mResampler->setSampleRate(mSampleRate);
- mResampler->setVolume(AudioMixer::UNITY_GAIN, AudioMixer::UNITY_GAIN);
- // resampler always outputs stereo
- mRsmpOutBuffer = new int32_t[mFrameCount * FCC_2];
- }
- mRsmpInIndex = mFrameCount;
+
+ // AudioRecord mSampleRate and mChannelCount are constant due to AudioRecord API constraints.
+ // But if thread's mSampleRate or mChannelCount changes, how will that affect active tracks?
}
uint32_t AudioFlinger::RecordThread::getInputFramesLost()
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 999fea3..fa3563c 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -270,8 +270,8 @@ protected:
const sp<AudioFlinger> mAudioFlinger;
- // updated by PlaybackThread::readOutputParameters() or
- // RecordThread::readInputParameters()
+ // updated by PlaybackThread::readOutputParameters_l() or
+ // RecordThread::readInputParameters_l()
uint32_t mSampleRate;
size_t mFrameCount; // output HAL, direct output, record
audio_channel_mask_t mChannelMask;
@@ -478,7 +478,7 @@ public:
status_t getTimestamp_l(AudioTimestamp& timestamp);
protected:
- // updated by readOutputParameters()
+ // updated by readOutputParameters_l()
size_t mNormalFrameCount; // normal mixer and effects
int16_t* mMixBuffer; // frame size aligned mix buffer
@@ -541,7 +541,7 @@ private:
void removeTrack_l(const sp<Track>& track);
void broadcast_l();
- void readOutputParameters();
+ void readOutputParameters_l();
virtual void dumpInternals(int fd, const Vector<String16>& args);
void dumpTracks(int fd, const Vector<String16>& args);
@@ -839,17 +839,28 @@ public:
// record thread
-class RecordThread : public ThreadBase, public AudioBufferProvider
- // derives from AudioBufferProvider interface for use by resampler
+class RecordThread : public ThreadBase
{
public:
+ class RecordTrack;
+ class ResamplerBufferProvider : public AudioBufferProvider
+ // derives from AudioBufferProvider interface for use by resampler
+ {
+ public:
+ ResamplerBufferProvider(RecordTrack* recordTrack) : mRecordTrack(recordTrack) { }
+ virtual ~ResamplerBufferProvider() { }
+ // AudioBufferProvider interface
+ virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts);
+ virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
+ private:
+ RecordTrack * const mRecordTrack;
+ };
+
#include "RecordTracks.h"
RecordThread(const sp<AudioFlinger>& audioFlinger,
AudioStreamIn *input,
- uint32_t sampleRate,
- audio_channel_mask_t channelMask,
audio_io_handle_t id,
audio_devices_t outDevice,
audio_devices_t inDevice
@@ -898,14 +909,11 @@ public:
AudioStreamIn* clearInput();
virtual audio_stream_t* stream() const;
- // AudioBufferProvider interface
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts);
- virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
virtual bool checkForNewParameters_l();
virtual String8 getParameters(const String8& keys);
virtual void audioConfigChanged_l(int event, int param = 0);
- void readInputParameters();
+ void readInputParameters_l();
virtual uint32_t getInputFramesLost();
virtual status_t addEffectChain_l(const sp<EffectChain>& chain);
@@ -921,14 +929,11 @@ public:
virtual bool isValidSyncEvent(const sp<SyncEvent>& event) const;
static void syncStartEventCallback(const wp<SyncEvent>& event);
- void handleSyncStartEvent(const sp<SyncEvent>& event);
virtual size_t frameCount() const { return mFrameCount; }
bool hasFastRecorder() const { return false; }
private:
- void clearSyncStartEvent();
-
// Enter standby if not already in standby, and set mStandby flag
void standbyIfNotAlreadyInStandby();
@@ -944,34 +949,13 @@ private:
int mActiveTracksGen;
Condition mStartStopCond;
- // updated by RecordThread::readInputParameters()
- AudioResampler *mResampler;
- // interleaved stereo pairs of fixed-point signed Q19.12
- int32_t *mRsmpOutBuffer;
-
// resampler converts input at HAL Hz to output at AudioRecord client Hz
int16_t *mRsmpInBuffer; // see new[] for details on the size
size_t mRsmpInFrames; // size of resampler input in frames
size_t mRsmpInFramesP2;// size rounded up to a power-of-2
- size_t mRsmpInUnrel; // unreleased frames remaining from
- // most recent getNextBuffer
- // these are rolling counters that are never cleared
- int32_t mRsmpInFront; // next available frame
+
+ // rolling index that is never cleared
int32_t mRsmpInRear; // last filled frame + 1
- size_t mRsmpInIndex; // FIXME legacy
-
- // client's requested configuration, which may differ from the HAL configuration
- const uint32_t mReqChannelCount;
- const uint32_t mReqSampleRate;
-
- ssize_t mBytesRead;
- // sync event triggering actual audio capture. Frames read before this event will
- // be dropped and therefore not read by the application.
- sp<SyncEvent> mSyncStartEvent;
- // number of captured frames to drop after the start sync event has been received.
- // when < 0, maximum frames to drop before starting capture even if sync event is
- // not received
- ssize_t mFramestoDrop;
// For dumpsys
const sp<NBAIO_Sink> mTeeSink;
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 05fde7c..58705c4 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -34,7 +34,9 @@ public:
RESUMING,
ACTIVE,
PAUSING,
- PAUSED
+ PAUSED,
+ STARTING_1, // for RecordTrack only
+ STARTING_2, // for RecordTrack only
};
TrackBase(ThreadBase *thread,
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index e5152b8..92ed46a 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -1785,17 +1785,34 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack(
int uid)
: TrackBase(thread, client, sampleRate, format,
channelMask, frameCount, 0 /*sharedBuffer*/, sessionId, uid, false /*isOut*/),
- mOverflow(false)
+ mOverflow(false), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpOutFrameCount(0),
+ // See real initialization of mRsmpInFront at RecordThread::start()
+ mRsmpInUnrel(0), mRsmpInFront(0), mFramesToDrop(0), mResamplerBufferProvider(NULL)
{
ALOGV("RecordTrack constructor");
if (mCblk != NULL) {
mServerProxy = new AudioRecordServerProxy(mCblk, mBuffer, frameCount, mFrameSize);
}
+
+ uint32_t channelCount = popcount(channelMask);
+ // FIXME I don't understand either of the channel count checks
+ if (thread->mSampleRate != sampleRate && thread->mChannelCount <= FCC_2 &&
+ channelCount <= FCC_2) {
+ // sink SR
+ mResampler = AudioResampler::create(16, thread->mChannelCount, sampleRate);
+ // source SR
+ mResampler->setSampleRate(thread->mSampleRate);
+ mResampler->setVolume(AudioMixer::UNITY_GAIN, AudioMixer::UNITY_GAIN);
+ mResamplerBufferProvider = new ResamplerBufferProvider(this);
+ }
}
AudioFlinger::RecordThread::RecordTrack::~RecordTrack()
{
ALOGV("%s", __func__);
+ delete mResampler;
+ delete[] mRsmpOutBuffer;
+ delete mResamplerBufferProvider;
}
// AudioBufferProvider interface
@@ -1868,12 +1885,12 @@ void AudioFlinger::RecordThread::RecordTrack::invalidate()
/*static*/ void AudioFlinger::RecordThread::RecordTrack::appendDumpHeader(String8& result)
{
- result.append(" Active Client Fmt Chn mask Session S Server fCount\n");
+ result.append(" Active Client Fmt Chn mask Session S Server fCount Resampling\n");
}
void AudioFlinger::RecordThread::RecordTrack::dump(char* buffer, size_t size, bool active)
{
- snprintf(buffer, size, " %6s %6u %3u %08X %7u %1d %08X %6zu\n",
+ snprintf(buffer, size, " %6s %6u %3u %08X %7u %1d %08X %6zu %10d\n",
active ? "yes" : "no",
(mClient == 0) ? getpid_cached : mClient->pid(),
mFormat,
@@ -1881,7 +1898,32 @@ void AudioFlinger::RecordThread::RecordTrack::dump(char* buffer, size_t size, bo
mSessionId,
mState,
mCblk->mServer,
- mFrameCount);
+ mFrameCount,
+ mResampler != NULL);
+
+}
+
+void AudioFlinger::RecordThread::RecordTrack::handleSyncStartEvent(const sp<SyncEvent>& event)
+{
+ if (event == mSyncStartEvent) {
+ ssize_t framesToDrop = 0;
+ sp<ThreadBase> threadBase = mThread.promote();
+ if (threadBase != 0) {
+ // TODO: use actual buffer filling status instead of 2 buffers when info is available
+ // from audio HAL
+ framesToDrop = threadBase->mFrameCount * 2;
+ }
+ mFramesToDrop = framesToDrop;
+ }
+}
+
+void AudioFlinger::RecordThread::RecordTrack::clearSyncStartEvent()
+{
+ if (mSyncStartEvent != 0) {
+ mSyncStartEvent->cancel();
+ mSyncStartEvent.clear();
+ }
+ mFramesToDrop = 0;
}
}; // namespace android
diff --git a/services/audioflinger/test-resample.cpp b/services/audioflinger/test-resample.cpp
index 66fcd90..3ab3ba9 100644
--- a/services/audioflinger/test-resample.cpp
+++ b/services/audioflinger/test-resample.cpp
@@ -27,6 +27,7 @@
#include <time.h>
#include <math.h>
#include <audio_utils/sndfile.h>
+#include <utils/Vector.h>
using namespace android;
@@ -34,7 +35,7 @@ bool gVerbose = false;
static int usage(const char* name) {
fprintf(stderr,"Usage: %s [-p] [-h] [-v] [-s] [-q {dq|lq|mq|hq|vhq|dlq|dmq|dhq}]"
- " [-i input-sample-rate] [-o output-sample-rate] [<input-file>]"
+ " [-i input-sample-rate] [-o output-sample-rate] [-O csv] [-P csv] [<input-file>]"
" <output-file>\n", name);
fprintf(stderr," -p enable profiling\n");
fprintf(stderr," -h create wav file\n");
@@ -51,9 +52,50 @@ static int usage(const char* name) {
fprintf(stderr," dhq : dynamic high quality\n");
fprintf(stderr," -i input file sample rate (ignored if input file is specified)\n");
fprintf(stderr," -o output file sample rate\n");
+ fprintf(stderr," -O # frames output per call to resample() in CSV format\n");
+ fprintf(stderr," -P # frames provided per call to resample() in CSV format\n");
return -1;
}
+// Convert a list of integers in CSV format to a Vector of those values.
+// Returns the number of elements in the list, or -1 on error.
+int parseCSV(const char *string, Vector<int>& values)
+{
+ // pass 1: count the number of values and do syntax check
+ size_t numValues = 0;
+ bool hadDigit = false;
+ for (const char *p = string; ; ) {
+ switch (*p++) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ hadDigit = true;
+ break;
+ case '\0':
+ if (hadDigit) {
+ // pass 2: allocate and initialize vector of values
+ values.resize(++numValues);
+ values.editItemAt(0) = atoi(p = optarg);
+ for (size_t i = 1; i < numValues; ) {
+ if (*p++ == ',') {
+ values.editItemAt(i++) = atoi(p);
+ }
+ }
+ return numValues;
+ }
+ // fall through
+ case ',':
+ if (hadDigit) {
+ hadDigit = false;
+ numValues++;
+ break;
+ }
+ // fall through
+ default:
+ return -1;
+ }
+ }
+}
+
int main(int argc, char* argv[]) {
const char* const progname = argv[0];
@@ -64,9 +106,11 @@ int main(int argc, char* argv[]) {
int input_freq = 0;
int output_freq = 0;
AudioResampler::src_quality quality = AudioResampler::DEFAULT_QUALITY;
+ Vector<int> Ovalues;
+ Vector<int> Pvalues;
int ch;
- while ((ch = getopt(argc, argv, "pfhvsq:i:o:")) != -1) {
+ while ((ch = getopt(argc, argv, "pfhvsq:i:o:O:P:")) != -1) {
switch (ch) {
case 'p':
profileResample = true;
@@ -111,6 +155,18 @@ int main(int argc, char* argv[]) {
case 'o':
output_freq = atoi(optarg);
break;
+ case 'O':
+ if (parseCSV(optarg, Ovalues) < 0) {
+ fprintf(stderr, "incorrect syntax for -O option\n");
+ return -1;
+ }
+ break;
+ case 'P':
+ if (parseCSV(optarg, Pvalues) < 0) {
+ fprintf(stderr, "incorrect syntax for -P option\n");
+ return -1;
+ }
+ break;
case '?':
default:
usage(progname);
@@ -177,12 +233,14 @@ int main(int argc, char* argv[]) {
const int mChannels;
size_t mNextFrame; // index of next frame to provide
size_t mUnrel; // number of frames not yet released
+ const Vector<int> mPvalues; // number of frames provided per call
+ size_t mNextPidx; // index of next entry in mPvalues to use
public:
- Provider(const void* addr, size_t size, int channels)
+ Provider(const void* addr, size_t size, int channels, const Vector<int>& Pvalues)
: mAddr((int16_t*) addr),
mNumFrames(size / (channels*sizeof(int16_t))),
mChannels(channels),
- mNextFrame(0), mUnrel(0) {
+ mNextFrame(0), mUnrel(0), mPvalues(Pvalues), mNextPidx(0) {
}
virtual status_t getNextBuffer(Buffer* buffer,
int64_t pts = kInvalidPTS) {
@@ -191,6 +249,16 @@ int main(int argc, char* argv[]) {
if (requestedFrames > mNumFrames - mNextFrame) {
buffer->frameCount = mNumFrames - mNextFrame;
}
+ if (!mPvalues.isEmpty()) {
+ size_t provided = mPvalues[mNextPidx++];
+ printf("mPvalue[%d]=%u not %u\n", mNextPidx-1, provided, buffer->frameCount);
+ if (provided < buffer->frameCount) {
+ buffer->frameCount = provided;
+ }
+ if (mNextPidx >= mPvalues.size()) {
+ mNextPidx = 0;
+ }
+ }
if (gVerbose) {
printf("getNextBuffer() requested %u frames out of %u frames available,"
" and returned %u frames\n",
@@ -225,7 +293,7 @@ int main(int argc, char* argv[]) {
void reset() {
mNextFrame = 0;
}
- } provider(input_vaddr, input_size, channels);
+ } provider(input_vaddr, input_size, channels, Pvalues);
size_t input_frames = input_size / (channels * sizeof(int16_t));
if (gVerbose) {
@@ -343,7 +411,20 @@ int main(int argc, char* argv[]) {
if (gVerbose) {
printf("resample() %u output frames\n", out_frames);
}
- resampler->resample((int*) output_vaddr, out_frames, &provider);
+ if (Ovalues.isEmpty()) {
+ Ovalues.push(out_frames);
+ }
+ for (size_t i = 0, j = 0; i < out_frames; ) {
+ size_t thisFrames = Ovalues[j++];
+ if (j >= Ovalues.size()) {
+ j = 0;
+ }
+ if (thisFrames == 0 || thisFrames > out_frames - i) {
+ thisFrames = out_frames - i;
+ }
+ resampler->resample((int*) output_vaddr + 2*i, thisFrames, &provider);
+ i += thisFrames;
+ }
if (gVerbose) {
printf("resample() complete\n");
}
diff --git a/services/camera/libcameraservice/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index 0a88a75..80b7cd4 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -407,12 +407,6 @@ void Camera2Client::disconnect() {
l.mParameters.state = Parameters::DISCONNECTED;
}
- mStreamingProcessor->deletePreviewStream();
- mStreamingProcessor->deleteRecordingStream();
- mJpegProcessor->deleteStream();
- mCallbackProcessor->deleteStream();
- mZslProcessor->deleteStream();
-
mStreamingProcessor->requestExit();
mFrameProcessor->requestExit();
mCaptureSequencer->requestExit();
@@ -429,6 +423,14 @@ void Camera2Client::disconnect() {
mZslProcessorThread->join();
mCallbackProcessor->join();
+ ALOGV("Camera %d: Deleting streams", mCameraId);
+
+ mStreamingProcessor->deletePreviewStream();
+ mStreamingProcessor->deleteRecordingStream();
+ mJpegProcessor->deleteStream();
+ mCallbackProcessor->deleteStream();
+ mZslProcessor->deleteStream();
+
ALOGV("Camera %d: Disconnecting device", mCameraId);
mDevice->disconnect();