Merge "Use float_from_q19_12 conversion in AudioMixer"
diff --git a/cmds/screenrecord/Android.mk b/cmds/screenrecord/Android.mk
index d77fdb6..6747e60 100644
--- a/cmds/screenrecord/Android.mk
+++ b/cmds/screenrecord/Android.mk
@@ -19,6 +19,7 @@
LOCAL_SRC_FILES := \
screenrecord.cpp \
EglWindow.cpp \
+ FrameOutput.cpp \
TextRenderer.cpp \
Overlay.cpp \
Program.cpp
diff --git a/cmds/screenrecord/EglWindow.cpp b/cmds/screenrecord/EglWindow.cpp
index aa0517f..c16f2ad 100644
--- a/cmds/screenrecord/EglWindow.cpp
+++ b/cmds/screenrecord/EglWindow.cpp
@@ -35,11 +35,16 @@
status_t EglWindow::createWindow(const sp<IGraphicBufferProducer>& surface) {
- status_t err = eglSetupContext();
+ if (mEglSurface != EGL_NO_SURFACE) {
+ ALOGE("surface already created");
+ return UNKNOWN_ERROR;
+ }
+ status_t err = eglSetupContext(false);
if (err != NO_ERROR) {
return err;
}
+ // Cache the current dimensions. We're not expecting these to change.
surface->query(NATIVE_WINDOW_WIDTH, &mWidth);
surface->query(NATIVE_WINDOW_HEIGHT, &mHeight);
@@ -56,6 +61,34 @@
return NO_ERROR;
}
+status_t EglWindow::createPbuffer(int width, int height) {
+ if (mEglSurface != EGL_NO_SURFACE) {
+ ALOGE("surface already created");
+ return UNKNOWN_ERROR;
+ }
+ status_t err = eglSetupContext(true);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ mWidth = width;
+ mHeight = height;
+
+ EGLint pbufferAttribs[] = {
+ EGL_WIDTH, width,
+ EGL_HEIGHT, height,
+ EGL_NONE
+ };
+ mEglSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, pbufferAttribs);
+ if (mEglSurface == EGL_NO_SURFACE) {
+ ALOGE("eglCreatePbufferSurface error: %#x", eglGetError());
+ eglRelease();
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
status_t EglWindow::makeCurrent() const {
if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
ALOGE("eglMakeCurrent failed: %#x", eglGetError());
@@ -64,7 +97,7 @@
return NO_ERROR;
}
-status_t EglWindow::eglSetupContext() {
+status_t EglWindow::eglSetupContext(bool forPbuffer) {
EGLBoolean result;
mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
@@ -82,17 +115,28 @@
ALOGV("Initialized EGL v%d.%d", majorVersion, minorVersion);
EGLint numConfigs = 0;
- EGLint configAttribs[] = {
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_RECORDABLE_ANDROID, 1,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_NONE
+ EGLint windowConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RECORDABLE_ANDROID, 1,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ // no alpha
+ EGL_NONE
};
- result = eglChooseConfig(mEglDisplay, configAttribs, &mEglConfig, 1,
- &numConfigs);
+ EGLint pbufferConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_NONE
+ };
+ result = eglChooseConfig(mEglDisplay,
+ forPbuffer ? pbufferConfigAttribs : windowConfigAttribs,
+ &mEglConfig, 1, &numConfigs);
if (result != EGL_TRUE) {
ALOGE("eglChooseConfig error: %#x", eglGetError());
return UNKNOWN_ERROR;
diff --git a/cmds/screenrecord/EglWindow.h b/cmds/screenrecord/EglWindow.h
index 02a2efc..69d0c31 100644
--- a/cmds/screenrecord/EglWindow.h
+++ b/cmds/screenrecord/EglWindow.h
@@ -44,6 +44,9 @@
// Creates an EGL window for the supplied surface.
status_t createWindow(const sp<IGraphicBufferProducer>& surface);
+ // Creates an EGL pbuffer surface.
+ status_t createPbuffer(int width, int height);
+
// Return width and height values (obtained from IGBP).
int getWidth() const { return mWidth; }
int getHeight() const { return mHeight; }
@@ -65,7 +68,7 @@
EglWindow& operator=(const EglWindow&);
// Init display, create config and context.
- status_t eglSetupContext();
+ status_t eglSetupContext(bool forPbuffer);
void eglRelease();
// Basic EGL goodies.
diff --git a/cmds/screenrecord/FrameOutput.cpp b/cmds/screenrecord/FrameOutput.cpp
new file mode 100644
index 0000000..b5cf2f9
--- /dev/null
+++ b/cmds/screenrecord/FrameOutput.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ScreenRecord"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "FrameOutput.h"
+
+using namespace android;
+
+static const bool kShowTiming = false; // set to "true" for debugging
+static const int kGlBytesPerPixel = 4; // GL_RGBA
+static const int kOutBytesPerPixel = 3; // RGB only
+
+inline void FrameOutput::setValueLE(uint8_t* buf, uint32_t value) {
+ // Since we're running on an Android device, we're (almost) guaranteed
+ // to be little-endian, and (almost) guaranteed that unaligned 32-bit
+ // writes will work without any performance penalty... but do it
+ // byte-by-byte anyway.
+ buf[0] = (uint8_t) value;
+ buf[1] = (uint8_t) (value >> 8);
+ buf[2] = (uint8_t) (value >> 16);
+ buf[3] = (uint8_t) (value >> 24);
+}
+
+status_t FrameOutput::createInputSurface(int width, int height,
+ sp<IGraphicBufferProducer>* pBufferProducer) {
+ status_t err;
+
+ err = mEglWindow.createPbuffer(width, height);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ mEglWindow.makeCurrent();
+
+ glViewport(0, 0, width, height);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+
+ // Shader for rendering the external texture.
+ err = mExtTexProgram.setup(Program::PROGRAM_EXTERNAL_TEXTURE);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // Input side (buffers from virtual display).
+ glGenTextures(1, &mExtTextureName);
+ if (mExtTextureName == 0) {
+ ALOGE("glGenTextures failed: %#x", glGetError());
+ return UNKNOWN_ERROR;
+ }
+
+ mBufferQueue = new BufferQueue(/*new GraphicBufferAlloc()*/);
+ mGlConsumer = new GLConsumer(mBufferQueue, mExtTextureName,
+ GL_TEXTURE_EXTERNAL_OES);
+ mGlConsumer->setName(String8("virtual display"));
+ mGlConsumer->setDefaultBufferSize(width, height);
+ mGlConsumer->setDefaultMaxBufferCount(5);
+ mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE);
+
+ mGlConsumer->setFrameAvailableListener(this);
+
+ mPixelBuf = new uint8_t[width * height * kGlBytesPerPixel];
+
+ *pBufferProducer = mBufferQueue;
+
+ ALOGD("FrameOutput::createInputSurface OK");
+ return NO_ERROR;
+}
+
+status_t FrameOutput::copyFrame(FILE* fp, long timeoutUsec) {
+ Mutex::Autolock _l(mMutex);
+ ALOGV("copyFrame %ld\n", timeoutUsec);
+
+ if (!mFrameAvailable) {
+ nsecs_t timeoutNsec = (nsecs_t)timeoutUsec * 1000;
+ int cc = mEventCond.waitRelative(mMutex, timeoutNsec);
+ if (cc == -ETIMEDOUT) {
+ ALOGV("cond wait timed out");
+ return ETIMEDOUT;
+ } else if (cc != 0) {
+ ALOGW("cond wait returned error %d", cc);
+ return cc;
+ }
+ }
+ if (!mFrameAvailable) {
+ // This happens when Ctrl-C is hit. Apparently POSIX says that the
+ // pthread wait call doesn't return EINTR, treating this instead as
+ // an instance of a "spurious wakeup". We didn't get a frame, so
+ // we just treat it as a timeout.
+ return ETIMEDOUT;
+ }
+
+ // A frame is available. Clear the flag for the next round.
+ mFrameAvailable = false;
+
+ float texMatrix[16];
+ mGlConsumer->updateTexImage();
+ mGlConsumer->getTransformMatrix(texMatrix);
+
+ // The data is in an external texture, so we need to render it to the
+ // pbuffer to get access to RGB pixel data. We also want to flip it
+ // upside-down for easy conversion to a bitmap.
+ int width = mEglWindow.getWidth();
+ int height = mEglWindow.getHeight();
+ status_t err = mExtTexProgram.blit(mExtTextureName, texMatrix, 0, 0,
+ width, height, true);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // GLES only guarantees that glReadPixels() will work with GL_RGBA, so we
+ // need to get 4 bytes/pixel and reduce it. Depending on the size of the
+ // screen and the device capabilities, this can take a while.
+ int64_t startWhenNsec, pixWhenNsec, endWhenNsec;
+ if (kShowTiming) {
+ startWhenNsec = systemTime(CLOCK_MONOTONIC);
+ }
+ GLenum glErr;
+ glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, mPixelBuf);
+ if ((glErr = glGetError()) != GL_NO_ERROR) {
+ ALOGE("glReadPixels failed: %#x", glErr);
+ return UNKNOWN_ERROR;
+ }
+ if (kShowTiming) {
+ pixWhenNsec = systemTime(CLOCK_MONOTONIC);
+ }
+ reduceRgbaToRgb(mPixelBuf, width * height);
+ if (kShowTiming) {
+ endWhenNsec = systemTime(CLOCK_MONOTONIC);
+ ALOGD("got pixels (get=%.3f ms, reduce=%.3fms)",
+ (pixWhenNsec - startWhenNsec) / 1000000.0,
+ (endWhenNsec - pixWhenNsec) / 1000000.0);
+ }
+
+ // Fill out the header.
+ size_t headerLen = sizeof(uint32_t) * 5;
+ size_t rgbDataLen = width * height * kOutBytesPerPixel;
+ size_t packetLen = headerLen - sizeof(uint32_t) + rgbDataLen;
+ uint8_t header[headerLen];
+ setValueLE(&header[0], packetLen);
+ setValueLE(&header[4], width);
+ setValueLE(&header[8], height);
+ setValueLE(&header[12], width * kOutBytesPerPixel);
+ setValueLE(&header[16], HAL_PIXEL_FORMAT_RGB_888);
+
+ // Currently using buffered I/O rather than writev(). Not expecting it
+ // to make much of a difference, but it might be worth a test for larger
+ // frame sizes.
+ if (kShowTiming) {
+ startWhenNsec = systemTime(CLOCK_MONOTONIC);
+ }
+ fwrite(header, 1, headerLen, fp);
+ fwrite(mPixelBuf, 1, rgbDataLen, fp);
+ fflush(fp);
+ if (kShowTiming) {
+ endWhenNsec = systemTime(CLOCK_MONOTONIC);
+ ALOGD("wrote pixels (%.3f ms)",
+ (endWhenNsec - startWhenNsec) / 1000000.0);
+ }
+
+ if (ferror(fp)) {
+ // errno may not be useful; log it anyway
+ ALOGE("write failed (errno=%d)", errno);
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+void FrameOutput::reduceRgbaToRgb(uint8_t* buf, unsigned int pixelCount) {
+ // Convert RGBA to RGB.
+ //
+ // Unaligned 32-bit accesses are allowed on ARM, so we could do this
+ // with 32-bit copies advancing at different rates (taking care at the
+ // end to not go one byte over).
+ const uint8_t* readPtr = buf;
+ for (unsigned int i = 0; i < pixelCount; i++) {
+ *buf++ = *readPtr++;
+ *buf++ = *readPtr++;
+ *buf++ = *readPtr++;
+ readPtr++;
+ }
+}
+
+// Callback; executes on arbitrary thread.
+void FrameOutput::onFrameAvailable() {
+ Mutex::Autolock _l(mMutex);
+ mFrameAvailable = true;
+ mEventCond.signal();
+}
diff --git a/cmds/screenrecord/FrameOutput.h b/cmds/screenrecord/FrameOutput.h
new file mode 100644
index 0000000..b8e9e68
--- /dev/null
+++ b/cmds/screenrecord/FrameOutput.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SCREENRECORD_FRAMEOUTPUT_H
+#define SCREENRECORD_FRAMEOUTPUT_H
+
+#include "Program.h"
+#include "EglWindow.h"
+
+#include <gui/BufferQueue.h>
+#include <gui/GLConsumer.h>
+
+namespace android {
+
+/*
+ * Support for "frames" output format.
+ */
+class FrameOutput : public GLConsumer::FrameAvailableListener {
+public:
+ FrameOutput() : mFrameAvailable(false),
+ mExtTextureName(0),
+ mPixelBuf(NULL)
+ {}
+ virtual ~FrameOutput() {
+ delete[] mPixelBuf;
+ }
+
+ // Create an "input surface", similar in purpose to a MediaCodec input
+ // surface, that the virtual display can send buffers to. Also configures
+ // EGL with a pbuffer surface on the current thread.
+ status_t createInputSurface(int width, int height,
+ sp<IGraphicBufferProducer>* pBufferProducer);
+
+ // Copy one from input to output. If no frame is available, this will wait up to the
+ // specified number of microseconds.
+ //
+ // Returns ETIMEDOUT if the timeout expired before we found a frame.
+ status_t copyFrame(FILE* fp, long timeoutUsec);
+
+ // Prepare to copy frames. Makes the EGL context used by this object current.
+ void prepareToCopy() {
+ mEglWindow.makeCurrent();
+ }
+
+private:
+ FrameOutput(const FrameOutput&);
+ FrameOutput& operator=(const FrameOutput&);
+
+ // (overrides GLConsumer::FrameAvailableListener method)
+ virtual void onFrameAvailable();
+
+ // Reduces RGBA to RGB, in place.
+ static void reduceRgbaToRgb(uint8_t* buf, unsigned int pixelCount);
+
+ // Put a 32-bit value into a buffer, in little-endian byte order.
+ static void setValueLE(uint8_t* buf, uint32_t value);
+
+ // Used to wait for the FrameAvailableListener callback.
+ Mutex mMutex;
+ Condition mEventCond;
+
+ // Set by the FrameAvailableListener callback.
+ bool mFrameAvailable;
+
+ // Our queue. The producer side is passed to the virtual display, the
+ // consumer side feeds into our GLConsumer.
+ sp<BufferQueue> mBufferQueue;
+
+ // This receives frames from the virtual display and makes them available
+ // as an external texture.
+ sp<GLConsumer> mGlConsumer;
+
+ // EGL display / context / surface.
+ EglWindow mEglWindow;
+
+ // GL rendering support.
+ Program mExtTexProgram;
+
+ // External texture, updated by GLConsumer.
+ GLuint mExtTextureName;
+
+ // Pixel data buffer.
+ uint8_t* mPixelBuf;
+};
+
+}; // namespace android
+
+#endif /*SCREENRECORD_FRAMEOUTPUT_H*/
diff --git a/cmds/screenrecord/Program.cpp b/cmds/screenrecord/Program.cpp
index a198204..73cae6e 100644
--- a/cmds/screenrecord/Program.cpp
+++ b/cmds/screenrecord/Program.cpp
@@ -201,7 +201,7 @@
status_t Program::blit(GLuint texName, const float* texMatrix,
- int32_t x, int32_t y, int32_t w, int32_t h) const {
+ int32_t x, int32_t y, int32_t w, int32_t h, bool invert) const {
ALOGV("Program::blit %d xy=%d,%d wh=%d,%d", texName, x, y, w, h);
const float pos[] = {
@@ -218,7 +218,7 @@
};
status_t err;
- err = beforeDraw(texName, texMatrix, pos, uv);
+ err = beforeDraw(texName, texMatrix, pos, uv, invert);
if (err == NO_ERROR) {
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
err = afterDraw();
@@ -232,7 +232,7 @@
status_t err;
- err = beforeDraw(texName, texMatrix, vertices, texes);
+ err = beforeDraw(texName, texMatrix, vertices, texes, false);
if (err == NO_ERROR) {
glDrawArrays(GL_TRIANGLES, 0, count);
err = afterDraw();
@@ -241,7 +241,7 @@
}
status_t Program::beforeDraw(GLuint texName, const float* texMatrix,
- const float* vertices, const float* texes) const {
+ const float* vertices, const float* texes, bool invert) const {
// Create an orthographic projection matrix based on viewport size.
GLint vp[4];
glGetIntegerv(GL_VIEWPORT, vp);
@@ -251,6 +251,10 @@
0.0f, 0.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
};
+ if (invert) {
+ screenToNdc[5] = -screenToNdc[5];
+ screenToNdc[13] = -screenToNdc[13];
+ }
glUseProgram(mProgram);
diff --git a/cmds/screenrecord/Program.h b/cmds/screenrecord/Program.h
index e47bc0d..558be8d 100644
--- a/cmds/screenrecord/Program.h
+++ b/cmds/screenrecord/Program.h
@@ -51,9 +51,11 @@
// Release the program and associated resources.
void release();
- // Blit the specified texture to { x, y, x+w, y+h }.
+ // Blit the specified texture to { x, y, x+w, y+h }. Inverts the
+ // content if "invert" is set.
status_t blit(GLuint texName, const float* texMatrix,
- int32_t x, int32_t y, int32_t w, int32_t h) const;
+ int32_t x, int32_t y, int32_t w, int32_t h,
+ bool invert = false) const;
// Draw a number of triangles.
status_t drawTriangles(GLuint texName, const float* texMatrix,
@@ -67,7 +69,7 @@
// Common code for draw functions.
status_t beforeDraw(GLuint texName, const float* texMatrix,
- const float* vertices, const float* texes) const;
+ const float* vertices, const float* texes, bool invert) const;
status_t afterDraw() const;
// GLES 2 shader utilities.
diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp
index b6f150c..a17fc51 100644
--- a/cmds/screenrecord/screenrecord.cpp
+++ b/cmds/screenrecord/screenrecord.cpp
@@ -50,6 +50,7 @@
#include "screenrecord.h"
#include "Overlay.h"
+#include "FrameOutput.h"
using namespace android;
@@ -58,11 +59,14 @@
static const uint32_t kMaxTimeLimitSec = 180; // 3 minutes
static const uint32_t kFallbackWidth = 1280; // 720p
static const uint32_t kFallbackHeight = 720;
+static const char* kMimeTypeAvc = "video/avc";
// Command-line parameters.
static bool gVerbose = false; // chatty on stdout
static bool gRotate = false; // rotate 90 degrees
-static bool gRawOutput = false; // generate raw H.264 byte stream output
+static enum {
+ FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES
+} gOutputFormat = FORMAT_MP4; // data format for output
static bool gSizeSpecified = false; // was size explicitly requested?
static bool gWantInfoScreen = false; // do we want initial info screen?
static bool gWantFrameTime = false; // do we want times on each frame?
@@ -142,14 +146,14 @@
status_t err;
if (gVerbose) {
- printf("Configuring recorder for %dx%d video at %.2fMbps\n",
- gVideoWidth, gVideoHeight, gBitRate / 1000000.0);
+ printf("Configuring recorder for %dx%d %s at %.2fMbps\n",
+ gVideoWidth, gVideoHeight, kMimeTypeAvc, gBitRate / 1000000.0);
}
sp<AMessage> format = new AMessage;
format->setInt32("width", gVideoWidth);
format->setInt32("height", gVideoHeight);
- format->setString("mime", "video/avc");
+ format->setString("mime", kMimeTypeAvc);
format->setInt32("color-format", OMX_COLOR_FormatAndroidOpaque);
format->setInt32("bitrate", gBitRate);
format->setFloat("frame-rate", displayFps);
@@ -159,16 +163,18 @@
looper->setName("screenrecord_looper");
looper->start();
ALOGV("Creating codec");
- sp<MediaCodec> codec = MediaCodec::CreateByType(looper, "video/avc", true);
+ sp<MediaCodec> codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true);
if (codec == NULL) {
- fprintf(stderr, "ERROR: unable to create video/avc codec instance\n");
+ fprintf(stderr, "ERROR: unable to create %s codec instance\n",
+ kMimeTypeAvc);
return UNKNOWN_ERROR;
}
err = codec->configure(format, NULL, NULL,
MediaCodec::CONFIGURE_FLAG_ENCODE);
if (err != NO_ERROR) {
- fprintf(stderr, "ERROR: unable to configure codec (err=%d)\n", err);
+ fprintf(stderr, "ERROR: unable to configure %s codec at %dx%d (err=%d)\n",
+ kMimeTypeAvc, gVideoWidth, gVideoHeight, err);
codec->release();
return err;
}
@@ -513,7 +519,7 @@
}
/*
- * Main "do work" method.
+ * Main "do work" start point.
*
* Configures codec, muxer, and virtual display, then starts moving bits
* around.
@@ -555,30 +561,40 @@
// Configure and start the encoder.
sp<MediaCodec> encoder;
+ sp<FrameOutput> frameOutput;
sp<IGraphicBufferProducer> encoderInputSurface;
- err = prepareEncoder(mainDpyInfo.fps, &encoder, &encoderInputSurface);
+ if (gOutputFormat != FORMAT_FRAMES) {
+ err = prepareEncoder(mainDpyInfo.fps, &encoder, &encoderInputSurface);
- if (err != NO_ERROR && !gSizeSpecified) {
- // fallback is defined for landscape; swap if we're in portrait
- bool needSwap = gVideoWidth < gVideoHeight;
- uint32_t newWidth = needSwap ? kFallbackHeight : kFallbackWidth;
- uint32_t newHeight = needSwap ? kFallbackWidth : kFallbackHeight;
- if (gVideoWidth != newWidth && gVideoHeight != newHeight) {
- ALOGV("Retrying with 720p");
- fprintf(stderr, "WARNING: failed at %dx%d, retrying at %dx%d\n",
- gVideoWidth, gVideoHeight, newWidth, newHeight);
- gVideoWidth = newWidth;
- gVideoHeight = newHeight;
- err = prepareEncoder(mainDpyInfo.fps, &encoder,
- &encoderInputSurface);
+ if (err != NO_ERROR && !gSizeSpecified) {
+ // fallback is defined for landscape; swap if we're in portrait
+ bool needSwap = gVideoWidth < gVideoHeight;
+ uint32_t newWidth = needSwap ? kFallbackHeight : kFallbackWidth;
+ uint32_t newHeight = needSwap ? kFallbackWidth : kFallbackHeight;
+ if (gVideoWidth != newWidth && gVideoHeight != newHeight) {
+ ALOGV("Retrying with 720p");
+ fprintf(stderr, "WARNING: failed at %dx%d, retrying at %dx%d\n",
+ gVideoWidth, gVideoHeight, newWidth, newHeight);
+ gVideoWidth = newWidth;
+ gVideoHeight = newHeight;
+ err = prepareEncoder(mainDpyInfo.fps, &encoder,
+ &encoderInputSurface);
+ }
+ }
+ if (err != NO_ERROR) return err;
+
+ // From here on, we must explicitly release() the encoder before it goes
+ // out of scope, or we will get an assertion failure from stagefright
+ // later on in a different thread.
+ } else {
+ // We're not using an encoder at all. The "encoder input surface" we hand to
+ // SurfaceFlinger will just feed directly to us.
+ frameOutput = new FrameOutput();
+ err = frameOutput->createInputSurface(gVideoWidth, gVideoHeight, &encoderInputSurface);
+ if (err != NO_ERROR) {
+ return err;
}
}
- if (err != NO_ERROR) return err;
-
- // From here on, we must explicitly release() the encoder before it goes
- // out of scope, or we will get an assertion failure from stagefright
- // later on in a different thread.
-
// Draw the "info" page by rendering a frame with GLES and sending
// it directly to the encoder.
@@ -595,7 +611,7 @@
overlay = new Overlay();
err = overlay->start(encoderInputSurface, &bufferProducer);
if (err != NO_ERROR) {
- encoder->release();
+ if (encoder != NULL) encoder->release();
return err;
}
if (gVerbose) {
@@ -610,46 +626,83 @@
sp<IBinder> dpy;
err = prepareVirtualDisplay(mainDpyInfo, bufferProducer, &dpy);
if (err != NO_ERROR) {
- encoder->release();
+ if (encoder != NULL) encoder->release();
return err;
}
sp<MediaMuxer> muxer = NULL;
FILE* rawFp = NULL;
- if (gRawOutput) {
- rawFp = prepareRawOutput(fileName);
- if (rawFp == NULL) {
- encoder->release();
- return -1;
+ switch (gOutputFormat) {
+ case FORMAT_MP4: {
+ // Configure muxer. We have to wait for the CSD blob from the encoder
+ // before we can start it.
+ muxer = new MediaMuxer(fileName, MediaMuxer::OUTPUT_FORMAT_MPEG_4);
+ if (gRotate) {
+ muxer->setOrientationHint(90); // TODO: does this do anything?
+ }
+ break;
+ }
+ case FORMAT_H264:
+ case FORMAT_FRAMES: {
+ rawFp = prepareRawOutput(fileName);
+ if (rawFp == NULL) {
+ if (encoder != NULL) encoder->release();
+ return -1;
+ }
+ break;
+ }
+ default:
+ fprintf(stderr, "ERROR: unknown format %d\n", gOutputFormat);
+ abort();
+ }
+
+ if (gOutputFormat == FORMAT_FRAMES) {
+ // TODO: if we want to make this a proper feature, we should output
+ // an outer header with version info. Right now we never change
+ // the frame size or format, so we could conceivably just send
+ // the current frame header once and then follow it with an
+ // unbroken stream of data.
+
+ // Make the EGL context current again. This gets unhooked if we're
+ // using "--bugreport" mode.
+ // TODO: figure out if we can eliminate this
+ frameOutput->prepareToCopy();
+
+ while (!gStopRequested) {
+ // Poll for frames, the same way we do for MediaCodec. We do
+ // all of the work on the main thread.
+ //
+ // Ideally we'd sleep indefinitely and wake when the
+ // stop was requested, but this will do for now. (It almost
+ // works because wait() wakes when a signal hits, but we
+ // need to handle the edge cases.)
+ err = frameOutput->copyFrame(rawFp, 250000);
+ if (err == ETIMEDOUT) {
+ err = NO_ERROR;
+ } else if (err != NO_ERROR) {
+ ALOGE("Got error %d from copyFrame()", err);
+ break;
+ }
}
} else {
- // Configure muxer. We have to wait for the CSD blob from the encoder
- // before we can start it.
- muxer = new MediaMuxer(fileName, MediaMuxer::OUTPUT_FORMAT_MPEG_4);
- if (gRotate) {
- muxer->setOrientationHint(90); // TODO: does this do anything?
+ // Main encoder loop.
+ err = runEncoder(encoder, muxer, rawFp, mainDpy, dpy,
+ mainDpyInfo.orientation);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "Encoder failed (err=%d)\n", err);
+ // fall through to cleanup
}
- }
- // Main encoder loop.
- err = runEncoder(encoder, muxer, rawFp, mainDpy, dpy,
- mainDpyInfo.orientation);
- if (err != NO_ERROR) {
- fprintf(stderr, "Encoder failed (err=%d)\n", err);
- // fall through to cleanup
- }
-
- if (gVerbose) {
- printf("Stopping encoder and muxer\n");
+ if (gVerbose) {
+ printf("Stopping encoder and muxer\n");
+ }
}
// Shut everything down, starting with the producer side.
encoderInputSurface = NULL;
SurfaceComposerClient::destroyDisplay(dpy);
- if (overlay != NULL) {
- overlay->stop();
- }
- encoder->stop();
+ if (overlay != NULL) overlay->stop();
+ if (encoder != NULL) encoder->stop();
if (muxer != NULL) {
// If we don't stop muxer explicitly, i.e. let the destructor run,
// it may hang (b/11050628).
@@ -657,7 +710,7 @@
} else if (rawFp != stdout) {
fclose(rawFp);
}
- encoder->release();
+ if (encoder != NULL) encoder->release();
return err;
}
@@ -819,11 +872,12 @@
{ "size", required_argument, NULL, 's' },
{ "bit-rate", required_argument, NULL, 'b' },
{ "time-limit", required_argument, NULL, 't' },
+ { "bugreport", no_argument, NULL, 'u' },
+ // "unofficial" options
{ "show-device-info", no_argument, NULL, 'i' },
{ "show-frame-time", no_argument, NULL, 'f' },
- { "bugreport", no_argument, NULL, 'u' },
{ "rotate", no_argument, NULL, 'r' },
- { "raw", no_argument, NULL, 'w' },
+ { "output-format", required_argument, NULL, 'o' },
{ NULL, 0, NULL, 0 }
};
@@ -875,23 +929,31 @@
return 2;
}
break;
+ case 'u':
+ gWantInfoScreen = true;
+ gWantFrameTime = true;
+ break;
case 'i':
gWantInfoScreen = true;
break;
case 'f':
gWantFrameTime = true;
break;
- case 'u':
- gWantInfoScreen = true;
- gWantFrameTime = true;
- break;
case 'r':
// experimental feature
gRotate = true;
break;
- case 'w':
- // experimental feature
- gRawOutput = true;
+ case 'o':
+ if (strcmp(optarg, "mp4") == 0) {
+ gOutputFormat = FORMAT_MP4;
+ } else if (strcmp(optarg, "h264") == 0) {
+ gOutputFormat = FORMAT_H264;
+ } else if (strcmp(optarg, "frames") == 0) {
+ gOutputFormat = FORMAT_FRAMES;
+ } else {
+ fprintf(stderr, "Unknown format '%s'\n", optarg);
+ return 2;
+ }
break;
default:
if (ic != '?') {
@@ -907,7 +969,7 @@
}
const char* fileName = argv[optind];
- if (!gRawOutput) {
+ if (gOutputFormat == FORMAT_MP4) {
// MediaMuxer tries to create the file in the constructor, but we don't
// learn about the failure until muxer.start(), which returns a generic
// error code without logging anything. We attempt to create the file
diff --git a/include/media/AudioRecord.h b/include/media/AudioRecord.h
index 7054fd4..b3c44a8 100644
--- a/include/media/AudioRecord.h
+++ b/include/media/AudioRecord.h
@@ -158,10 +158,10 @@
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- int frameCount = 0,
+ size_t frameCount = 0,
callback_t cbf = NULL,
void* user = NULL,
- int notificationFrames = 0,
+ uint32_t notificationFrames = 0,
int sessionId = AUDIO_SESSION_ALLOCATE,
transfer_type transferType = TRANSFER_DEFAULT,
audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE);
@@ -191,10 +191,10 @@
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- int frameCount = 0,
+ size_t frameCount = 0,
callback_t cbf = NULL,
void* user = NULL,
- int notificationFrames = 0,
+ uint32_t notificationFrames = 0,
bool threadCanCallJava = false,
int sessionId = AUDIO_SESSION_ALLOCATE,
transfer_type transferType = TRANSFER_DEFAULT,
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index d0df710..7e9d557 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -182,11 +182,11 @@
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t,
- int frameCount = 0,
+ size_t frameCount = 0,
audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE,
callback_t cbf = NULL,
void* user = NULL,
- int notificationFrames = 0,
+ uint32_t notificationFrames = 0,
int sessionId = AUDIO_SESSION_ALLOCATE,
transfer_type transferType = TRANSFER_DEFAULT,
const audio_offload_info_t *offloadInfo = NULL,
@@ -212,7 +212,7 @@
audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE,
callback_t cbf = NULL,
void* user = NULL,
- int notificationFrames = 0,
+ uint32_t notificationFrames = 0,
int sessionId = AUDIO_SESSION_ALLOCATE,
transfer_type transferType = TRANSFER_DEFAULT,
const audio_offload_info_t *offloadInfo = NULL,
@@ -245,11 +245,11 @@
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- int frameCount = 0,
+ size_t frameCount = 0,
audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE,
callback_t cbf = NULL,
void* user = NULL,
- int notificationFrames = 0,
+ uint32_t notificationFrames = 0,
const sp<IMemory>& sharedBuffer = 0,
bool threadCanCallJava = false,
int sessionId = AUDIO_SESSION_ALLOCATE,
@@ -284,7 +284,7 @@
size_t frameSize() const { return mFrameSize; }
uint32_t channelCount() const { return mChannelCount; }
- uint32_t frameCount() const { return mFrameCount; }
+ size_t frameCount() const { return mFrameCount; }
/* Return the static buffer specified in constructor or set(), or 0 for streaming mode */
sp<IMemory> sharedBuffer() const { return mSharedBuffer; }
diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h
index 7f53bfc..7c5f33a 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -169,7 +169,7 @@
audio_channel_mask_t *pChannelMask) = 0;
virtual status_t closeInput(audio_io_handle_t input) = 0;
- virtual status_t setStreamOutput(audio_stream_type_t stream, audio_io_handle_t output) = 0;
+ virtual status_t invalidateStream(audio_stream_type_t stream) = 0;
virtual status_t setVoiceVolume(float volume) = 0;
diff --git a/libvideoeditor/lvpp/Android.mk b/libvideoeditor/lvpp/Android.mk
index 2286827..860d351 100755
--- a/libvideoeditor/lvpp/Android.mk
+++ b/libvideoeditor/lvpp/Android.mk
@@ -71,7 +71,6 @@
$(TOP)/frameworks/av/media/libstagefright \
$(TOP)/frameworks/av/media/libstagefright/include \
$(TOP)/frameworks/av/media/libstagefright/rtsp \
- $(call include-path-for, corecg graphics) \
$(TOP)/frameworks/av/libvideoeditor/osal/inc \
$(TOP)/frameworks/av/libvideoeditor/vss/common/inc \
$(TOP)/frameworks/av/libvideoeditor/vss/mcs/inc \
diff --git a/libvideoeditor/vss/stagefrightshells/src/Android.mk b/libvideoeditor/vss/stagefrightshells/src/Android.mk
index e30b85d..9188942 100755
--- a/libvideoeditor/vss/stagefrightshells/src/Android.mk
+++ b/libvideoeditor/vss/stagefrightshells/src/Android.mk
@@ -33,7 +33,6 @@
$(TOP)/frameworks/av/media/libstagefright \
$(TOP)/frameworks/av/media/libstagefright/include \
$(TOP)/frameworks/av/media/libstagefright/rtsp \
- $(call include-path-for, corecg graphics) \
$(TOP)/frameworks/av/libvideoeditor/lvpp \
$(TOP)/frameworks/av/libvideoeditor/osal/inc \
$(TOP)/frameworks/av/libvideoeditor/vss/inc \
diff --git a/media/libeffects/visualizer/Android.mk b/media/libeffects/visualizer/Android.mk
index dd2d306..c92c543 100644
--- a/media/libeffects/visualizer/Android.mk
+++ b/media/libeffects/visualizer/Android.mk
@@ -17,7 +17,6 @@
LOCAL_MODULE:= libvisualizer
LOCAL_C_INCLUDES := \
- $(call include-path-for, graphics corecg) \
$(call include-path-for, audio-effects)
diff --git a/media/libeffects/visualizer/EffectVisualizer.cpp b/media/libeffects/visualizer/EffectVisualizer.cpp
index 2d66eef..5bdaa03 100644
--- a/media/libeffects/visualizer/EffectVisualizer.cpp
+++ b/media/libeffects/visualizer/EffectVisualizer.cpp
@@ -544,56 +544,57 @@
break;
- case VISUALIZER_CMD_CAPTURE:
- if (pReplyData == NULL || *replySize != pContext->mCaptureSize) {
- ALOGV("VISUALIZER_CMD_CAPTURE() error *replySize %d pContext->mCaptureSize %d",
- *replySize, pContext->mCaptureSize);
+ case VISUALIZER_CMD_CAPTURE: {
+ int32_t captureSize = pContext->mCaptureSize;
+ if (pReplyData == NULL || *replySize != captureSize) {
+ ALOGV("VISUALIZER_CMD_CAPTURE() error *replySize %d captureSize %d",
+ *replySize, captureSize);
return -EINVAL;
}
if (pContext->mState == VISUALIZER_STATE_ACTIVE) {
- int32_t latencyMs = pContext->mLatency;
const uint32_t deltaMs = Visualizer_getDeltaTimeMsFromUpdatedTime(pContext);
- latencyMs -= deltaMs;
- if (latencyMs < 0) {
- latencyMs = 0;
- }
- const uint32_t deltaSmpl = pContext->mConfig.inputCfg.samplingRate * latencyMs / 1000;
-
- int32_t capturePoint = pContext->mCaptureIdx - pContext->mCaptureSize - deltaSmpl;
- int32_t captureSize = pContext->mCaptureSize;
- if (capturePoint < 0) {
- int32_t size = -capturePoint;
- if (size > captureSize) {
- size = captureSize;
- }
- memcpy(pReplyData,
- pContext->mCaptureBuf + CAPTURE_BUF_SIZE + capturePoint,
- size);
- pReplyData = (char *)pReplyData + size;
- captureSize -= size;
- capturePoint = 0;
- }
- memcpy(pReplyData,
- pContext->mCaptureBuf + capturePoint,
- captureSize);
-
// if audio framework has stopped playing audio although the effect is still
// active we must clear the capture buffer to return silence
if ((pContext->mLastCaptureIdx == pContext->mCaptureIdx) &&
- (pContext->mBufferUpdateTime.tv_sec != 0)) {
- if (deltaMs > MAX_STALL_TIME_MS) {
+ (pContext->mBufferUpdateTime.tv_sec != 0) &&
+ (deltaMs > MAX_STALL_TIME_MS)) {
ALOGV("capture going to idle");
pContext->mBufferUpdateTime.tv_sec = 0;
- memset(pReplyData, 0x80, pContext->mCaptureSize);
+ memset(pReplyData, 0x80, captureSize);
+ } else {
+ int32_t latencyMs = pContext->mLatency;
+ latencyMs -= deltaMs;
+ if (latencyMs < 0) {
+ latencyMs = 0;
}
+ const uint32_t deltaSmpl =
+ pContext->mConfig.inputCfg.samplingRate * latencyMs / 1000;
+ int32_t capturePoint = pContext->mCaptureIdx - captureSize - deltaSmpl;
+
+ if (capturePoint < 0) {
+ int32_t size = -capturePoint;
+ if (size > captureSize) {
+ size = captureSize;
+ }
+ memcpy(pReplyData,
+ pContext->mCaptureBuf + CAPTURE_BUF_SIZE + capturePoint,
+ size);
+ pReplyData = (char *)pReplyData + size;
+ captureSize -= size;
+ capturePoint = 0;
+ }
+ memcpy(pReplyData,
+ pContext->mCaptureBuf + capturePoint,
+ captureSize);
}
+
pContext->mLastCaptureIdx = pContext->mCaptureIdx;
} else {
- memset(pReplyData, 0x80, pContext->mCaptureSize);
+ memset(pReplyData, 0x80, captureSize);
}
- break;
+ } break;
case VISUALIZER_CMD_MEASURE: {
uint16_t peakU16 = 0;
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index e0acae6..f3770e4 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -72,7 +72,6 @@
LOCAL_MODULE:= libmedia
LOCAL_C_INCLUDES := \
- $(call include-path-for, graphics corecg) \
$(TOP)/frameworks/native/include/media/openmax \
external/icu4c/common \
external/icu4c/i18n \
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 4438dfd..961b0a2 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -73,10 +73,10 @@
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- int frameCount,
+ size_t frameCount,
callback_t cbf,
void* user,
- int notificationFrames,
+ uint32_t notificationFrames,
int sessionId,
transfer_type transferType,
audio_input_flags_t flags __unused)
@@ -114,18 +114,18 @@
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- int frameCountInt,
+ size_t frameCount,
callback_t cbf,
void* user,
- int notificationFrames,
+ uint32_t notificationFrames,
bool threadCanCallJava,
int sessionId,
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,
+ ALOGV("set(): inputSource %d, sampleRate %u, format %#x, channelMask %#x, frameCount %zu, "
+ "notificationFrames %u, sessionId %d, transferType %d, flags %#x",
+ inputSource, sampleRate, format, channelMask, frameCount, notificationFrames,
sessionId, transferType, flags);
switch (transferType) {
@@ -151,13 +151,6 @@
}
mTransfer = transferType;
- // FIXME "int" here is legacy and will be replaced by size_t later
- if (frameCountInt < 0) {
- ALOGE("Invalid frame count %d", frameCountInt);
- return BAD_VALUE;
- }
- size_t frameCount = frameCountInt;
-
AutoMutex lock(mLock);
// invariant that mAudioRecord != 0 is true only after set() returns successfully
@@ -240,22 +233,27 @@
ALOGV("set(): mSessionId %d", mSessionId);
mFlags = flags;
-
- // create the IAudioRecord
- status = openRecord_l(0 /*epoch*/);
- if (status != NO_ERROR) {
- return status;
- }
+ mCbf = cbf;
if (cbf != NULL) {
mAudioRecordThread = new AudioRecordThread(*this, threadCanCallJava);
mAudioRecordThread->run("AudioRecord", ANDROID_PRIORITY_AUDIO);
}
- mStatus = NO_ERROR;
+ // create the IAudioRecord
+ status = openRecord_l(0 /*epoch*/);
+ if (status != NO_ERROR) {
+ if (mAudioRecordThread != 0) {
+ mAudioRecordThread->requestExit(); // see comment in AudioRecord.h
+ mAudioRecordThread->requestExitAndWait();
+ mAudioRecordThread.clear();
+ }
+ return status;
+ }
+
+ mStatus = NO_ERROR;
mActive = false;
- mCbf = cbf;
mUserData = user;
// TODO: add audio hardware input latency here
mLatency = (1000*mFrameCount) / sampleRate;
@@ -430,22 +428,37 @@
return NO_INIT;
}
- IAudioFlinger::track_flags_t trackFlags = IAudioFlinger::TRACK_DEFAULT;
- pid_t tid = -1;
+ // Fast tracks must be at the primary _output_ [sic] sampling rate,
+ // because there is currently no concept of a primary input sampling rate
+ uint32_t afSampleRate = AudioSystem::getPrimaryOutputSamplingRate();
+ if (afSampleRate == 0) {
+ ALOGW("getPrimaryOutputSamplingRate failed");
+ }
// Client can only express a preference for FAST. Server will perform additional tests.
- // The only supported use case for FAST is callback transfer mode.
+ if ((mFlags & AUDIO_INPUT_FLAG_FAST) && !(
+ // use case: callback transfer mode
+ (mTransfer == TRANSFER_CALLBACK) &&
+ // matching sample rate
+ (mSampleRate == afSampleRate))) {
+ ALOGW("AUDIO_INPUT_FLAG_FAST denied by client");
+ // once denied, do not request again if IAudioRecord is re-created
+ mFlags = (audio_input_flags_t) (mFlags & ~AUDIO_INPUT_FLAG_FAST);
+ }
+
+ IAudioFlinger::track_flags_t trackFlags = IAudioFlinger::TRACK_DEFAULT;
+
+ pid_t tid = -1;
if (mFlags & AUDIO_INPUT_FLAG_FAST) {
- if ((mTransfer != TRANSFER_CALLBACK) || (mAudioRecordThread == 0)) {
- ALOGW("AUDIO_INPUT_FLAG_FAST denied by client");
- // once denied, do not request again if IAudioRecord is re-created
- mFlags = (audio_input_flags_t) (mFlags & ~AUDIO_INPUT_FLAG_FAST);
- } else {
- trackFlags |= IAudioFlinger::TRACK_FAST;
+ trackFlags |= IAudioFlinger::TRACK_FAST;
+ if (mAudioRecordThread != 0) {
tid = mAudioRecordThread->getTid();
}
}
+ // FIXME Assume double buffering, because we don't know the true HAL sample rate
+ const uint32_t nBuffering = 2;
+
mNotificationFramesAct = mNotificationFramesReq;
size_t frameCount = mReqFrameCount;
@@ -517,23 +530,21 @@
}
frameCount = temp;
- // FIXME missing fast track frameCount logic
mAwaitBoost = false;
if (mFlags & AUDIO_INPUT_FLAG_FAST) {
if (trackFlags & IAudioFlinger::TRACK_FAST) {
- ALOGV("AUDIO_INPUT_FLAG_FAST successful; frameCount %u", mFrameCount);
+ ALOGV("AUDIO_INPUT_FLAG_FAST successful; frameCount %u", frameCount);
mAwaitBoost = true;
- // double-buffering is not required for fast tracks, due to tighter scheduling
- if (mNotificationFramesAct == 0 || mNotificationFramesAct > mFrameCount) {
- mNotificationFramesAct = mFrameCount;
- }
} else {
- ALOGV("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %u", mFrameCount);
+ ALOGV("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %u", frameCount);
// once denied, do not request again if IAudioRecord is re-created
mFlags = (audio_input_flags_t) (mFlags & ~AUDIO_INPUT_FLAG_FAST);
- if (mNotificationFramesAct == 0 || mNotificationFramesAct > mFrameCount/2) {
- mNotificationFramesAct = mFrameCount/2;
- }
+ }
+ // Theoretically double-buffering is not required for fast tracks,
+ // due to tighter scheduling. But in practice, to accomodate kernels with
+ // scheduling jitter, and apps with computation jitter, we use double-buffering.
+ if (mNotificationFramesAct == 0 || mNotificationFramesAct > frameCount/nBuffering) {
+ mNotificationFramesAct = frameCount/nBuffering;
}
}
@@ -803,7 +814,7 @@
}
// Cache other fields that will be needed soon
- size_t notificationFrames = mNotificationFramesAct;
+ uint32_t notificationFrames = mNotificationFramesAct;
if (mRefreshRemaining) {
mRefreshRemaining = false;
mRemainingFrames = notificationFrames;
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index adf3847..d25c40b 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -108,11 +108,11 @@
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- int frameCount,
+ size_t frameCount,
audio_output_flags_t flags,
callback_t cbf,
void* user,
- int notificationFrames,
+ uint32_t notificationFrames,
int sessionId,
transfer_type transferType,
const audio_offload_info_t *offloadInfo,
@@ -138,7 +138,7 @@
audio_output_flags_t flags,
callback_t cbf,
void* user,
- int notificationFrames,
+ uint32_t notificationFrames,
int sessionId,
transfer_type transferType,
const audio_offload_info_t *offloadInfo,
@@ -182,11 +182,11 @@
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
- int frameCountInt,
+ size_t frameCount,
audio_output_flags_t flags,
callback_t cbf,
void* user,
- int notificationFrames,
+ uint32_t notificationFrames,
const sp<IMemory>& sharedBuffer,
bool threadCanCallJava,
int sessionId,
@@ -195,9 +195,9 @@
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,
+ ALOGV("set(): streamType %d, sampleRate %u, format %#x, channelMask %#x, frameCount %zu, "
+ "flags #%x, notificationFrames %u, sessionId %d, transferType %d",
+ streamType, sampleRate, format, channelMask, frameCount, flags, notificationFrames,
sessionId, transferType);
switch (transferType) {
@@ -236,13 +236,6 @@
mSharedBuffer = sharedBuffer;
mTransfer = transferType;
- // FIXME "int" here is legacy and will be replaced by size_t later
- if (frameCountInt < 0) {
- ALOGE("Invalid frame count %d", frameCountInt);
- return BAD_VALUE;
- }
- size_t frameCount = frameCountInt;
-
ALOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(),
sharedBuffer->size());
@@ -892,8 +885,8 @@
// either of these use cases:
// use case 1: shared buffer
(mSharedBuffer != 0) ||
- // use case 2: callback handler
- (mCbf != NULL)) &&
+ // use case 2: callback transfer mode
+ (mTransfer == TRANSFER_CALLBACK)) &&
// matching sample rate
(mSampleRate == afSampleRate))) {
ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by client");
@@ -1487,7 +1480,7 @@
// Cache other fields that will be needed soon
uint32_t loopPeriod = mLoopPeriod;
uint32_t sampleRate = mSampleRate;
- size_t notificationFrames = mNotificationFramesAct;
+ uint32_t notificationFrames = mNotificationFramesAct;
if (mRefreshRemaining) {
mRefreshRemaining = false;
mRemainingFrames = notificationFrames;
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index e696323..a9a9f1a 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -58,7 +58,7 @@
RESTORE_OUTPUT,
OPEN_INPUT,
CLOSE_INPUT,
- SET_STREAM_OUTPUT,
+ INVALIDATE_STREAM,
SET_VOICE_VOLUME,
GET_RENDER_POSITION,
GET_INPUT_FRAMES_LOST,
@@ -545,13 +545,12 @@
return reply.readInt32();
}
- virtual status_t setStreamOutput(audio_stream_type_t stream, audio_io_handle_t output)
+ virtual status_t invalidateStream(audio_stream_type_t stream)
{
Parcel data, reply;
data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
data.writeInt32((int32_t) stream);
- data.writeInt32((int32_t) output);
- remote()->transact(SET_STREAM_OUTPUT, data, &reply);
+ remote()->transact(INVALIDATE_STREAM, data, &reply);
return reply.readInt32();
}
@@ -1044,11 +1043,10 @@
reply->writeInt32(closeInput((audio_io_handle_t) data.readInt32()));
return NO_ERROR;
} break;
- case SET_STREAM_OUTPUT: {
+ case INVALIDATE_STREAM: {
CHECK_INTERFACE(IAudioFlinger, data, reply);
- uint32_t stream = data.readInt32();
- audio_io_handle_t output = (audio_io_handle_t) data.readInt32();
- reply->writeInt32(setStreamOutput((audio_stream_type_t) stream, output));
+ audio_stream_type_t stream = (audio_stream_type_t) data.readInt32();
+ reply->writeInt32(invalidateStream(stream));
return NO_ERROR;
} break;
case SET_VOICE_VOLUME: {
diff --git a/media/libmedia/IMediaHTTPConnection.cpp b/media/libmedia/IMediaHTTPConnection.cpp
index 22c470a..7e26ee6 100644
--- a/media/libmedia/IMediaHTTPConnection.cpp
+++ b/media/libmedia/IMediaHTTPConnection.cpp
@@ -95,7 +95,10 @@
data.writeInt32(size);
status_t err = remote()->transact(READ_AT, data, &reply);
- CHECK_EQ(err, (status_t)OK);
+ if (err != OK) {
+ ALOGE("remote readAt failed");
+ return UNKNOWN_ERROR;
+ }
int32_t exceptionCode = reply.readExceptionCode();
diff --git a/media/libmedia/JetPlayer.cpp b/media/libmedia/JetPlayer.cpp
index e914b34..f0f1832 100644
--- a/media/libmedia/JetPlayer.cpp
+++ b/media/libmedia/JetPlayer.cpp
@@ -90,7 +90,7 @@
pLibConfig->sampleRate,
AUDIO_FORMAT_PCM_16_BIT,
audio_channel_out_mask_from_count(pLibConfig->numChannels),
- mTrackBufferSize,
+ (size_t) mTrackBufferSize,
AUDIO_OUTPUT_FLAG_NONE);
// create render and playback thread
diff --git a/media/libmedia/SoundPool.cpp b/media/libmedia/SoundPool.cpp
index 4885b4f..a55e09c 100644
--- a/media/libmedia/SoundPool.cpp
+++ b/media/libmedia/SoundPool.cpp
@@ -587,7 +587,7 @@
uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5);
uint32_t totalFrames = (kDefaultBufferCount * afFrameCount * sampleRate) / afSampleRate;
uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
- uint32_t frameCount = 0;
+ size_t frameCount = 0;
if (loop) {
frameCount = sample->size()/numChannels/
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index 8f21632..4189a5e 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -45,7 +45,6 @@
libstagefright_rtsp \
LOCAL_C_INCLUDES := \
- $(call include-path-for, graphics corecg) \
$(TOP)/frameworks/av/media/libstagefright/include \
$(TOP)/frameworks/av/media/libstagefright/rtsp \
$(TOP)/frameworks/av/media/libstagefright/wifi-display \
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 142788d..200c561 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1455,7 +1455,7 @@
format, bufferCount, mSessionId, flags);
uint32_t afSampleRate;
size_t afFrameCount;
- uint32_t frameCount;
+ size_t frameCount;
// offloading is only supported in callback mode for now.
// offloadInfo must be present if offload flag is set
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index df7da0a..d0e0e8e 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -65,7 +65,7 @@
if (status == OK) {
// make sure that the AudioRecord callback never returns more than the maximum
// buffer size
- int frameCount = kMaxBufferSize / sizeof(int16_t) / channelCount;
+ uint32_t frameCount = kMaxBufferSize / sizeof(int16_t) / channelCount;
// make sure that the AudioRecord total buffer size is large enough
size_t bufCount = 2;
@@ -76,10 +76,10 @@
mRecord = new AudioRecord(
inputSource, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
audio_channel_in_mask_from_count(channelCount),
- bufCount * frameCount,
+ (size_t) (bufCount * frameCount),
AudioRecordCallbackFunction,
this,
- frameCount);
+ frameCount /*notificationFrames*/);
mInitCheck = mRecord->initCheck();
} else {
mInitCheck = status;
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index e83ec62..4bad14b 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -2217,6 +2217,10 @@
mLock.unlock();
status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders);
+ // force connection at this point, to avoid a race condition between getMIMEType and the
+ // caching datasource constructed below, which could result in multiple requests to the
+ // server, and/or failed connections.
+ String8 contentType = mConnectingDataSource->getMIMEType();
mLock.lock();
if (err != OK) {
@@ -2247,8 +2251,6 @@
mConnectingDataSource.clear();
- String8 contentType = dataSource->getMIMEType();
-
if (strncasecmp(contentType.string(), "audio/", 6)) {
// We're not doing this for streams that appear to be audio-only
// streams to ensure that even low bandwidth streams start
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 95779c4..2af4682 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -40,6 +40,8 @@
#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
+#include <utils/Mutex.h>
+
#include <ctype.h>
#include <openssl/aes.h>
#include <openssl/md5.h>
@@ -56,10 +58,14 @@
mHTTPDataSource(new MediaHTTP(mHTTPService->makeHTTPConnection())),
mPrevBandwidthIndex(-1),
mStreamMask(0),
+ mNewStreamMask(0),
+ mSwapMask(0),
mCheckBandwidthGeneration(0),
+ mSwitchGeneration(0),
mLastDequeuedTimeUs(0ll),
mRealTimeBaseUs(0ll),
mReconfigurationInProgress(false),
+ mSwitchInProgress(false),
mDisconnectReplyID(0) {
mStreams[kAudioIndex] = StreamItem("audio");
@@ -68,16 +74,37 @@
for (size_t i = 0; i < kMaxStreams; ++i) {
mPacketSources.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
+ mPacketSources2.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
}
}
LiveSession::~LiveSession() {
}
+sp<ABuffer> LiveSession::createFormatChangeBuffer(bool swap) {
+ ABuffer *discontinuity = new ABuffer(0);
+ discontinuity->meta()->setInt32("discontinuity", ATSParser::DISCONTINUITY_FORMATCHANGE);
+ discontinuity->meta()->setInt32("swapPacketSource", swap);
+ discontinuity->meta()->setInt32("switchGeneration", mSwitchGeneration);
+ discontinuity->meta()->setInt64("timeUs", -1);
+ return discontinuity;
+}
+
+void LiveSession::swapPacketSource(StreamType stream) {
+ sp<AnotherPacketSource> &aps = mPacketSources.editValueFor(stream);
+ sp<AnotherPacketSource> &aps2 = mPacketSources2.editValueFor(stream);
+ sp<AnotherPacketSource> tmp = aps;
+ aps = aps2;
+ aps2 = tmp;
+ aps2->clear();
+}
+
status_t LiveSession::dequeueAccessUnit(
StreamType stream, sp<ABuffer> *accessUnit) {
if (!(mStreamMask & stream)) {
- return UNKNOWN_ERROR;
+ // return -EWOULDBLOCK to avoid halting the decoder
+ // when switching between audio/video and audio only.
+ return -EWOULDBLOCK;
}
sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream);
@@ -117,6 +144,25 @@
streamStr,
type,
extra == NULL ? "NULL" : extra->debugString().c_str());
+
+ int32_t swap;
+ if (type == ATSParser::DISCONTINUITY_FORMATCHANGE
+ && (*accessUnit)->meta()->findInt32("swapPacketSource", &swap)
+ && swap) {
+
+ int32_t switchGeneration;
+ CHECK((*accessUnit)->meta()->findInt32("switchGeneration", &switchGeneration));
+ {
+ Mutex::Autolock lock(mSwapMutex);
+ if (switchGeneration == mSwitchGeneration) {
+ swapPacketSource(stream);
+ sp<AMessage> msg = new AMessage(kWhatSwapped, id());
+ msg->setInt32("stream", stream);
+ msg->setInt32("switchGeneration", switchGeneration);
+ msg->post();
+ }
+ }
+ }
} else if (err == OK) {
if (stream == STREAMTYPE_AUDIO || stream == STREAMTYPE_VIDEO) {
int64_t timeUs;
@@ -138,6 +184,7 @@
}
status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) {
+ // No swapPacketSource race condition; called from the same thread as dequeueAccessUnit.
if (!(mStreamMask & stream)) {
return UNKNOWN_ERROR;
}
@@ -234,7 +281,12 @@
if (what == PlaylistFetcher::kWhatStopped) {
AString uri;
CHECK(msg->findString("uri", &uri));
- mFetcherInfos.removeItem(uri);
+ if (mFetcherInfos.removeItem(uri) < 0) {
+ // ignore duplicated kWhatStopped messages.
+ break;
+ }
+
+ tryToFinishBandwidthSwitch();
}
if (mContinuation != NULL) {
@@ -270,6 +322,8 @@
postPrepared(err);
}
+ cancelBandwidthSwitch();
+
mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(err);
mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(err);
@@ -308,6 +362,27 @@
break;
}
+ case PlaylistFetcher::kWhatStartedAt:
+ {
+ int32_t switchGeneration;
+ CHECK(msg->findInt32("switchGeneration", &switchGeneration));
+
+ if (switchGeneration != mSwitchGeneration) {
+ break;
+ }
+
+ // Resume fetcher for the original variant; the resumed fetcher should
+ // continue until the timestamps found in msg, which is stored by the
+ // new fetcher to indicate where the new variant has started buffering.
+ for (size_t i = 0; i < mFetcherInfos.size(); i++) {
+ const FetcherInfo info = mFetcherInfos.valueAt(i);
+ if (info.mToBeRemoved) {
+ info.mFetcher->resumeUntilAsync(msg);
+ }
+ }
+ break;
+ }
+
default:
TRESPASS();
}
@@ -352,6 +427,11 @@
break;
}
+ case kWhatSwapped:
+ {
+ onSwapped(msg);
+ break;
+ }
default:
TRESPASS();
break;
@@ -462,6 +542,10 @@
// during disconnection either.
cancelCheckBandwidthEvent();
+ // Protect mPacketSources from a swapPacketSource race condition through disconnect.
+ // (finishDisconnect, onFinishDisconnect2)
+ cancelBandwidthSwitch();
+
for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
mFetcherInfos.valueAt(i).mFetcher->stopAsync();
}
@@ -501,11 +585,13 @@
sp<AMessage> notify = new AMessage(kWhatFetcherNotify, id());
notify->setString("uri", uri);
+ notify->setInt32("switchGeneration", mSwitchGeneration);
FetcherInfo info;
info.mFetcher = new PlaylistFetcher(notify, this, uri);
info.mDurationUs = -1ll;
info.mIsPrepared = false;
+ info.mToBeRemoved = false;
looper()->registerHandler(info.mFetcher);
mFetcherInfos.add(uri, info);
@@ -845,8 +931,25 @@
return err;
}
+bool LiveSession::canSwitchUp() {
+ // Allow upwards bandwidth switch when a stream has buffered at least 10 seconds.
+ status_t err = OK;
+ for (size_t i = 0; i < mPacketSources.size(); ++i) {
+ sp<AnotherPacketSource> source = mPacketSources.valueAt(i);
+ int64_t dur = source->getBufferedDurationUs(&err);
+ if (err == OK && dur > 10000000) {
+ return true;
+ }
+ }
+ return false;
+}
+
void LiveSession::changeConfiguration(
int64_t timeUs, size_t bandwidthIndex, bool pickTrack) {
+ // Protect mPacketSources from a swapPacketSource race condition through reconfiguration.
+ // (changeConfiguration, onChangeConfiguration2, onChangeConfiguration3).
+ cancelBandwidthSwitch();
+
CHECK(!mReconfigurationInProgress);
mReconfigurationInProgress = true;
@@ -862,7 +965,8 @@
CHECK_LT(bandwidthIndex, mBandwidthItems.size());
const BandwidthItem &item = mBandwidthItems.itemAt(bandwidthIndex);
- uint32_t streamMask = 0;
+ uint32_t streamMask = 0; // streams that should be fetched by the new fetcher
+ uint32_t resumeMask = 0; // streams that should be fetched by the original fetcher
AString URIs[kMaxStreams];
for (size_t i = 0; i < kMaxStreams; ++i) {
@@ -880,9 +984,14 @@
// If we're seeking all current fetchers are discarded.
if (timeUs < 0ll) {
+ // delay fetcher removal
+ discardFetcher = false;
+
for (size_t j = 0; j < kMaxStreams; ++j) {
- if ((streamMask & indexToType(j)) && uri == URIs[j]) {
- discardFetcher = false;
+ StreamType type = indexToType(j);
+ if ((streamMask & type) && uri == URIs[j]) {
+ resumeMask |= type;
+ streamMask &= ~type;
}
}
}
@@ -894,8 +1003,15 @@
}
}
- sp<AMessage> msg = new AMessage(kWhatChangeConfiguration2, id());
+ sp<AMessage> msg;
+ if (timeUs < 0ll) {
+ // skip onChangeConfiguration2 (decoder destruction) if switching.
+ msg = new AMessage(kWhatChangeConfiguration3, id());
+ } else {
+ msg = new AMessage(kWhatChangeConfiguration2, id());
+ }
msg->setInt32("streamMask", streamMask);
+ msg->setInt32("resumeMask", resumeMask);
msg->setInt64("timeUs", timeUs);
for (size_t i = 0; i < kMaxStreams; ++i) {
if (streamMask & indexToType(i)) {
@@ -978,11 +1094,13 @@
}
void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
+ mContinuation.clear();
// All remaining fetchers are still suspended, the player has shutdown
// any decoders that needed it.
- uint32_t streamMask;
+ uint32_t streamMask, resumeMask;
CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
+ CHECK(msg->findInt32("resumeMask", (int32_t *)&resumeMask));
for (size_t i = 0; i < kMaxStreams; ++i) {
if (streamMask & indexToType(i)) {
@@ -991,37 +1109,39 @@
}
int64_t timeUs;
+ bool switching = false;
CHECK(msg->findInt64("timeUs", &timeUs));
if (timeUs < 0ll) {
timeUs = mLastDequeuedTimeUs;
+ switching = true;
}
mRealTimeBaseUs = ALooper::GetNowUs() - timeUs;
- mStreamMask = streamMask;
+ mNewStreamMask = streamMask;
- // Resume all existing fetchers and assign them packet sources.
+ // Of all existing fetchers:
+ // * Resume fetchers that are still needed and assign them original packet sources.
+ // * Mark otherwise unneeded fetchers for removal.
+ ALOGV("resuming fetchers for mask 0x%08x", resumeMask);
for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
const AString &uri = mFetcherInfos.keyAt(i);
- uint32_t resumeMask = 0;
-
sp<AnotherPacketSource> sources[kMaxStreams];
for (size_t j = 0; j < kMaxStreams; ++j) {
- if ((streamMask & indexToType(j)) && uri == mStreams[j].mUri) {
+ if ((resumeMask & indexToType(j)) && uri == mStreams[j].mUri) {
sources[j] = mPacketSources.valueFor(indexToType(j));
- resumeMask |= indexToType(j);
}
}
- CHECK_NE(resumeMask, 0u);
-
- ALOGV("resuming fetchers for mask 0x%08x", resumeMask);
-
- streamMask &= ~resumeMask;
-
- mFetcherInfos.valueAt(i).mFetcher->startAsync(
- sources[kAudioIndex], sources[kVideoIndex], sources[kSubtitleIndex]);
+ FetcherInfo &info = mFetcherInfos.editValueAt(i);
+ if (sources[kAudioIndex] != NULL || sources[kVideoIndex] != NULL
+ || sources[kSubtitleIndex] != NULL) {
+ info.mFetcher->startAsync(
+ sources[kAudioIndex], sources[kVideoIndex], sources[kSubtitleIndex]);
+ } else {
+ info.mToBeRemoved = true;
+ }
}
// streamMask now only contains the types that need a new fetcher created.
@@ -1030,6 +1150,8 @@
ALOGV("creating new fetchers for mask 0x%08x", streamMask);
}
+ // Find out when the original fetchers have buffered up to and start the new fetchers
+ // at a later timestamp.
for (size_t i = 0; i < kMaxStreams; i++) {
if (!(indexToType(i) & streamMask)) {
continue;
@@ -1041,12 +1163,40 @@
sp<PlaylistFetcher> fetcher = addFetcher(uri.c_str());
CHECK(fetcher != NULL);
+ int32_t latestSeq = -1;
+ int64_t latestTimeUs = 0ll;
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();
+
+ if (!switching) {
+ sources[j]->clear();
+ } else {
+ int32_t type, seq;
+ int64_t srcTimeUs;
+ sp<AMessage> meta = sources[j]->getLatestMeta();
+
+ if (meta != NULL && !meta->findInt32("discontinuity", &type)) {
+ CHECK(meta->findInt32("seq", &seq));
+ if (seq > latestSeq) {
+ latestSeq = seq;
+ }
+ CHECK(meta->findInt64("timeUs", &srcTimeUs));
+ if (srcTimeUs > latestTimeUs) {
+ latestTimeUs = srcTimeUs;
+ }
+ }
+
+ sources[j] = mPacketSources2.valueFor(indexToType(j));
+ sources[j]->clear();
+ uint32_t extraStreams = mNewStreamMask & (~mStreamMask);
+ if (extraStreams & indexToType(j)) {
+ sources[j]->queueAccessUnit(createFormatChangeBuffer(/* swap = */ false));
+ }
+ }
streamMask &= ~indexToType(j);
}
@@ -1056,7 +1206,9 @@
sources[kAudioIndex],
sources[kVideoIndex],
sources[kSubtitleIndex],
- timeUs);
+ timeUs,
+ latestTimeUs /* min start time(us) */,
+ latestSeq >= 0 ? latestSeq + 1 : -1 /* starting sequence number hint */ );
}
// All fetchers have now been started, the configuration change
@@ -1065,14 +1217,61 @@
scheduleCheckBandwidthEvent();
ALOGV("XXX configuration change completed.");
-
mReconfigurationInProgress = false;
+ if (switching) {
+ mSwitchInProgress = true;
+ } else {
+ mStreamMask = mNewStreamMask;
+ }
if (mDisconnectReplyID != 0) {
finishDisconnect();
}
}
+void LiveSession::onSwapped(const sp<AMessage> &msg) {
+ int32_t switchGeneration;
+ CHECK(msg->findInt32("switchGeneration", &switchGeneration));
+ if (switchGeneration != mSwitchGeneration) {
+ return;
+ }
+
+ int32_t stream;
+ CHECK(msg->findInt32("stream", &stream));
+ mSwapMask |= stream;
+ if (mSwapMask != mStreamMask) {
+ return;
+ }
+
+ // Check if new variant contains extra streams.
+ uint32_t extraStreams = mNewStreamMask & (~mStreamMask);
+ while (extraStreams) {
+ StreamType extraStream = (StreamType) (extraStreams & ~(extraStreams - 1));
+ swapPacketSource(extraStream);
+ extraStreams &= ~extraStream;
+ }
+
+ tryToFinishBandwidthSwitch();
+}
+
+// Mark switch done when:
+// 1. all old buffers are swapped out, AND
+// 2. all old fetchers are removed.
+void LiveSession::tryToFinishBandwidthSwitch() {
+ bool needToRemoveFetchers = false;
+ for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+ if (mFetcherInfos.valueAt(i).mToBeRemoved) {
+ needToRemoveFetchers = true;
+ break;
+ }
+ }
+ if (!needToRemoveFetchers && mSwapMask == mStreamMask) {
+ mStreamMask = mNewStreamMask;
+ mSwitchInProgress = false;
+ mSwapMask = 0;
+ }
+}
+
void LiveSession::scheduleCheckBandwidthEvent() {
sp<AMessage> msg = new AMessage(kWhatCheckBandwidth, id());
msg->setInt32("generation", mCheckBandwidthGeneration);
@@ -1083,16 +1282,37 @@
++mCheckBandwidthGeneration;
}
-void LiveSession::onCheckBandwidth() {
- if (mReconfigurationInProgress) {
- scheduleCheckBandwidthEvent();
- return;
+void LiveSession::cancelBandwidthSwitch() {
+ Mutex::Autolock lock(mSwapMutex);
+ mSwitchGeneration++;
+ mSwitchInProgress = false;
+ mSwapMask = 0;
+}
+
+bool LiveSession::canSwitchBandwidthTo(size_t bandwidthIndex) {
+ if (mReconfigurationInProgress || mSwitchInProgress) {
+ return false;
}
+ if (mPrevBandwidthIndex < 0) {
+ return true;
+ }
+
+ if (bandwidthIndex == (size_t)mPrevBandwidthIndex) {
+ return false;
+ } else if (bandwidthIndex > (size_t)mPrevBandwidthIndex) {
+ return canSwitchUp();
+ } else {
+ return true;
+ }
+}
+
+void LiveSession::onCheckBandwidth() {
size_t bandwidthIndex = getBandwidthIndex();
- if (mPrevBandwidthIndex < 0
- || bandwidthIndex != (size_t)mPrevBandwidthIndex) {
+ if (canSwitchBandwidthTo(bandwidthIndex)) {
changeConfiguration(-1ll /* timeUs */, bandwidthIndex);
+ } else {
+ scheduleCheckBandwidthEvent();
}
// Handling the kWhatCheckBandwidth even here does _not_ automatically
diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h
index c4d125c..91bbcea 100644
--- a/media/libstagefright/httplive/LiveSession.h
+++ b/media/libstagefright/httplive/LiveSession.h
@@ -83,6 +83,11 @@
kWhatPreparationFailed,
};
+ // create a format-change discontinuity
+ //
+ // swap:
+ // whether is format-change discontinuity should trigger a buffer swap
+ sp<ABuffer> createFormatChangeBuffer(bool swap = true);
protected:
virtual ~LiveSession();
@@ -101,6 +106,7 @@
kWhatChangeConfiguration2 = 'chC2',
kWhatChangeConfiguration3 = 'chC3',
kWhatFinishDisconnect2 = 'fin2',
+ kWhatSwapped = 'swap',
};
struct BandwidthItem {
@@ -112,6 +118,7 @@
sp<PlaylistFetcher> mFetcher;
int64_t mDurationUs;
bool mIsPrepared;
+ bool mToBeRemoved;
};
struct StreamItem {
@@ -146,9 +153,26 @@
KeyedVector<AString, FetcherInfo> mFetcherInfos;
uint32_t mStreamMask;
+ // Masks used during reconfiguration:
+ // mNewStreamMask: streams in the variant playlist we're switching to;
+ // we don't want to immediately overwrite the original value.
+ uint32_t mNewStreamMask;
+
+ // mSwapMask: streams that have started to playback content in the new variant playlist;
+ // we use this to track reconfiguration progress.
+ uint32_t mSwapMask;
+
KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources;
+ // A second set of packet sources that buffer content for the variant we're switching to.
+ KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources2;
+
+ // A mutex used to serialize two sets of events:
+ // * the swapping of packet sources in dequeueAccessUnit on the player thread, AND
+ // * a forced bandwidth switch termination in cancelSwitch on the live looper.
+ Mutex mSwapMutex;
int32_t mCheckBandwidthGeneration;
+ int32_t mSwitchGeneration;
size_t mContinuationCounter;
sp<AMessage> mContinuation;
@@ -157,6 +181,7 @@
int64_t mRealTimeBaseUs;
bool mReconfigurationInProgress;
+ bool mSwitchInProgress;
uint32_t mDisconnectReplyID;
sp<PlaylistFetcher> addFetcher(const char *uri);
@@ -199,16 +224,27 @@
void onChangeConfiguration(const sp<AMessage> &msg);
void onChangeConfiguration2(const sp<AMessage> &msg);
void onChangeConfiguration3(const sp<AMessage> &msg);
+ void onSwapped(const sp<AMessage> &msg);
+ void tryToFinishBandwidthSwitch();
void scheduleCheckBandwidthEvent();
void cancelCheckBandwidthEvent();
+ // cancelBandwidthSwitch is atomic wrt swapPacketSource; call it to prevent packet sources
+ // from being swapped out on stale discontinuities while manipulating
+ // mPacketSources/mPacketSources2.
+ void cancelBandwidthSwitch();
+
+ bool canSwitchBandwidthTo(size_t bandwidthIndex);
void onCheckBandwidth();
void finishDisconnect();
void postPrepared(status_t err);
+ void swapPacketSource(StreamType stream);
+ bool canSwitchUp();
+
DISALLOW_EVIL_CONSTRUCTORS(LiveSession);
};
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
index 030cbde..1a02e85 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.cpp
+++ b/media/libstagefright/httplive/PlaylistFetcher.cpp
@@ -48,16 +48,20 @@
// static
const int64_t PlaylistFetcher::kMinBufferedDurationUs = 10000000ll;
const int64_t PlaylistFetcher::kMaxMonitorDelayUs = 3000000ll;
+const int32_t PlaylistFetcher::kNumSkipFrames = 10;
PlaylistFetcher::PlaylistFetcher(
const sp<AMessage> ¬ify,
const sp<LiveSession> &session,
const char *uri)
: mNotify(notify),
+ mStartTimeUsNotify(notify->dup()),
mSession(session),
mURI(uri),
mStreamTypeMask(0),
mStartTimeUs(-1ll),
+ mMinStartTimeUs(0ll),
+ mStopParams(NULL),
mLastPlaylistFetchTimeUs(-1ll),
mSeqNumber(-1),
mNumRetries(0),
@@ -69,6 +73,8 @@
mFirstPTSValid(false),
mAbsoluteTimeAnchorUs(0ll) {
memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
+ mStartTimeUsNotify->setInt32("what", kWhatStartedAt);
+ mStartTimeUsNotify->setInt32("streamMask", 0);
}
PlaylistFetcher::~PlaylistFetcher() {
@@ -325,7 +331,9 @@
const sp<AnotherPacketSource> &audioSource,
const sp<AnotherPacketSource> &videoSource,
const sp<AnotherPacketSource> &subtitleSource,
- int64_t startTimeUs) {
+ int64_t startTimeUs,
+ int64_t minStartTimeUs,
+ int32_t startSeqNumberHint) {
sp<AMessage> msg = new AMessage(kWhatStart, id());
uint32_t streamTypeMask = 0ul;
@@ -347,6 +355,8 @@
msg->setInt32("streamTypeMask", streamTypeMask);
msg->setInt64("startTimeUs", startTimeUs);
+ msg->setInt64("minStartTimeUs", minStartTimeUs);
+ msg->setInt32("startSeqNumberHint", startSeqNumberHint);
msg->post();
}
@@ -358,6 +368,12 @@
(new AMessage(kWhatStop, id()))->post();
}
+void PlaylistFetcher::resumeUntilAsync(const sp<AMessage> ¶ms) {
+ AMessage* msg = new AMessage(kWhatResumeUntil, id());
+ msg->setMessage("params", params);
+ msg->post();
+}
+
void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatStart:
@@ -392,6 +408,7 @@
}
case kWhatMonitorQueue:
+ case kWhatDownloadNext:
{
int32_t generation;
CHECK(msg->findInt32("generation", &generation));
@@ -401,7 +418,17 @@
break;
}
- onMonitorQueue();
+ if (msg->what() == kWhatMonitorQueue) {
+ onMonitorQueue();
+ } else {
+ onDownloadNext();
+ }
+ break;
+ }
+
+ case kWhatResumeUntil:
+ {
+ onResumeUntil(msg);
break;
}
@@ -417,7 +444,10 @@
CHECK(msg->findInt32("streamTypeMask", (int32_t *)&streamTypeMask));
int64_t startTimeUs;
+ int32_t startSeqNumberHint;
CHECK(msg->findInt64("startTimeUs", &startTimeUs));
+ CHECK(msg->findInt64("minStartTimeUs", (int64_t *) &mMinStartTimeUs));
+ CHECK(msg->findInt32("startSeqNumberHint", &startSeqNumberHint));
if (streamTypeMask & LiveSession::STREAMTYPE_AUDIO) {
void *ptr;
@@ -455,6 +485,10 @@
mPrepared = false;
}
+ if (startSeqNumberHint >= 0) {
+ mSeqNumber = startSeqNumberHint;
+ }
+
postMonitorQueue();
return OK;
@@ -462,22 +496,72 @@
void PlaylistFetcher::onPause() {
cancelMonitorQueue();
-
- mPacketSources.clear();
- mStreamTypeMask = 0;
}
void PlaylistFetcher::onStop() {
cancelMonitorQueue();
- for (size_t i = 0; i < mPacketSources.size(); ++i) {
- mPacketSources.valueAt(i)->clear();
- }
-
mPacketSources.clear();
mStreamTypeMask = 0;
}
+// Resume until we have reached the boundary timestamps listed in `msg`; when
+// the remaining time is too short (within a resume threshold) stop immediately
+// instead.
+status_t PlaylistFetcher::onResumeUntil(const sp<AMessage> &msg) {
+ sp<AMessage> params;
+ CHECK(msg->findMessage("params", ¶ms));
+
+ bool stop = false;
+ for (size_t i = 0; i < mPacketSources.size(); i++) {
+ sp<AnotherPacketSource> packetSource = mPacketSources.valueAt(i);
+
+ const char *stopKey;
+ int streamType = mPacketSources.keyAt(i);
+ switch (streamType) {
+ case LiveSession::STREAMTYPE_VIDEO:
+ stopKey = "timeUsVideo";
+ break;
+
+ case LiveSession::STREAMTYPE_AUDIO:
+ stopKey = "timeUsAudio";
+ break;
+
+ case LiveSession::STREAMTYPE_SUBTITLES:
+ stopKey = "timeUsSubtitle";
+ break;
+
+ default:
+ TRESPASS();
+ }
+
+ // Don't resume if we would stop within a resume threshold.
+ int64_t latestTimeUs = 0, stopTimeUs = 0;
+ sp<AMessage> latestMeta = packetSource->getLatestMeta();
+ if (latestMeta != NULL
+ && (latestMeta->findInt64("timeUs", &latestTimeUs)
+ && params->findInt64(stopKey, &stopTimeUs))) {
+ int64_t diffUs = stopTimeUs - latestTimeUs;
+ if (diffUs < resumeThreshold(latestMeta)) {
+ stop = true;
+ }
+ }
+ }
+
+ if (stop) {
+ for (size_t i = 0; i < mPacketSources.size(); i++) {
+ mPacketSources.valueAt(i)->queueAccessUnit(mSession->createFormatChangeBuffer());
+ }
+ stopAsync();
+ return OK;
+ }
+
+ mStopParams = params;
+ postMonitorQueue();
+
+ return OK;
+}
+
void PlaylistFetcher::notifyError(status_t err) {
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatError);
@@ -519,8 +603,9 @@
packetSource->getBufferedDurationUs(&finalResult);
finalResult = OK;
} else {
- bool first = true;
-
+ // Use max stream duration to prevent us from waiting on a non-existent stream;
+ // when we cannot make out from the manifest what streams are included in a playlist
+ // we might assume extra streams.
for (size_t i = 0; i < mPacketSources.size(); ++i) {
if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0) {
continue;
@@ -528,9 +613,10 @@
int64_t bufferedStreamDurationUs =
mPacketSources.valueAt(i)->getBufferedDurationUs(&finalResult);
- if (first || bufferedStreamDurationUs < bufferedDurationUs) {
+ ALOGV("buffered %lld for stream %d",
+ bufferedStreamDurationUs, mPacketSources.keyAt(i));
+ if (bufferedStreamDurationUs > bufferedDurationUs) {
bufferedDurationUs = bufferedStreamDurationUs;
- first = false;
}
}
}
@@ -550,7 +636,12 @@
if (finalResult == OK && downloadMore) {
ALOGV("monitoring, buffered=%lld < %lld",
bufferedDurationUs, durationToBufferUs);
- onDownloadNext();
+ // delay the next download slightly; hopefully this gives other concurrent fetchers
+ // a better chance to run.
+ // onDownloadNext();
+ sp<AMessage> msg = new AMessage(kWhatDownloadNext, id());
+ msg->setInt32("generation", mMonitorQueueGeneration);
+ msg->post(1000l);
} else {
// Nothing to do yet, try again in a second.
@@ -617,6 +708,12 @@
const int32_t lastSeqNumberInPlaylist =
firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
+ if (mStartup && mSeqNumber >= 0
+ && (mSeqNumber < firstSeqNumberInPlaylist || mSeqNumber > lastSeqNumberInPlaylist)) {
+ // in case we guessed wrong during reconfiguration, try fetching the latest content.
+ mSeqNumber = lastSeqNumberInPlaylist;
+ }
+
if (mSeqNumber < 0) {
CHECK_GE(mStartTimeUs, 0ll);
@@ -762,6 +859,18 @@
err = extractAndQueueAccessUnits(buffer, itemMeta);
+ if (err == -EAGAIN) {
+ // bad starting sequence number hint
+ postMonitorQueue();
+ return;
+ }
+
+ if (err == ERROR_OUT_OF_RANGE) {
+ // reached stopping point
+ stopAsync();
+ return;
+ }
+
if (err != OK) {
notifyError(err);
return;
@@ -818,12 +927,15 @@
}
if (mTSParser == NULL) {
- mTSParser = new ATSParser;
+ // Use TS_TIMESTAMPS_ARE_ABSOLUTE so pts carry over between fetchers.
+ mTSParser = new ATSParser(ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE);
}
if (mNextPTSTimeUs >= 0ll) {
sp<AMessage> extra = new AMessage;
- extra->setInt64(IStreamListener::kKeyMediaTimeUs, mNextPTSTimeUs);
+ // Since we are using absolute timestamps, signal an offset of 0 to prevent
+ // ATSParser from skewing the timestamps of access units.
+ extra->setInt64(IStreamListener::kKeyMediaTimeUs, 0);
mTSParser->signalDiscontinuity(
ATSParser::DISCONTINUITY_SEEK, extra);
@@ -842,17 +954,23 @@
offset += 188;
}
+ status_t err = OK;
for (size_t i = mPacketSources.size(); i-- > 0;) {
sp<AnotherPacketSource> packetSource = mPacketSources.valueAt(i);
+ const char *key;
ATSParser::SourceType type;
- switch (mPacketSources.keyAt(i)) {
+ const LiveSession::StreamType stream = mPacketSources.keyAt(i);
+ switch (stream) {
+
case LiveSession::STREAMTYPE_VIDEO:
type = ATSParser::VIDEO;
+ key = "timeUsVideo";
break;
case LiveSession::STREAMTYPE_AUDIO:
type = ATSParser::AUDIO;
+ key = "timeUsAudio";
break;
case LiveSession::STREAMTYPE_SUBTITLES:
@@ -879,19 +997,87 @@
continue;
}
+ int64_t timeUs;
sp<ABuffer> accessUnit;
status_t finalResult;
while (source->hasBufferAvailable(&finalResult)
&& source->dequeueAccessUnit(&accessUnit) == OK) {
- // Note that we do NOT dequeue any discontinuities.
+
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+ if (mMinStartTimeUs > 0) {
+ if (timeUs < mMinStartTimeUs) {
+ // TODO untested path
+ // try a later ts
+ int32_t targetDuration;
+ mPlaylist->meta()->findInt32("target-duration", &targetDuration);
+ int32_t incr = (mMinStartTimeUs - timeUs) / 1000000 / targetDuration;
+ if (incr == 0) {
+ // increment mSeqNumber by at least one
+ incr = 1;
+ }
+ mSeqNumber += incr;
+ err = -EAGAIN;
+ break;
+ } else {
+ int64_t startTimeUs;
+ if (mStartTimeUsNotify != NULL
+ && !mStartTimeUsNotify->findInt64(key, &startTimeUs)) {
+ mStartTimeUsNotify->setInt64(key, timeUs);
+
+ uint32_t streamMask = 0;
+ mStartTimeUsNotify->findInt32("streamMask", (int32_t *) &streamMask);
+ streamMask |= mPacketSources.keyAt(i);
+ mStartTimeUsNotify->setInt32("streamMask", streamMask);
+
+ if (streamMask == mStreamTypeMask) {
+ mStartTimeUsNotify->post();
+ mStartTimeUsNotify.clear();
+ }
+ }
+ }
+ }
+
+ if (mStopParams != NULL) {
+ // Queue discontinuity in original stream.
+ int64_t stopTimeUs;
+ if (!mStopParams->findInt64(key, &stopTimeUs) || timeUs >= stopTimeUs) {
+ packetSource->queueAccessUnit(mSession->createFormatChangeBuffer());
+ mStreamTypeMask &= ~stream;
+ mPacketSources.removeItemsAt(i);
+ break;
+ }
+ }
+
+ // Note that we do NOT dequeue any discontinuities except for format change.
// for simplicity, store a reference to the format in each unit
sp<MetaData> format = source->getFormat();
if (format != NULL) {
accessUnit->meta()->setObject("format", format);
}
+
+ // Stash the sequence number so we can hint future fetchers where to start at.
+ accessUnit->meta()->setInt32("seq", mSeqNumber);
packetSource->queueAccessUnit(accessUnit);
}
+
+ if (err != OK) {
+ break;
+ }
+ }
+
+ if (err != OK) {
+ for (size_t i = mPacketSources.size(); i-- > 0;) {
+ sp<AnotherPacketSource> packetSource = mPacketSources.valueAt(i);
+ packetSource->clear();
+ }
+ return err;
+ }
+
+ if (!mStreamTypeMask) {
+ // Signal gap is filled between original and new stream.
+ ALOGV("ERROR OUT OF RANGE");
+ return ERROR_OUT_OF_RANGE;
}
return OK;
@@ -908,6 +1094,7 @@
CHECK(itemMeta->findInt64("durationUs", &durationUs));
buffer->meta()->setInt64("timeUs", getSegmentStartTimeUs(mSeqNumber));
buffer->meta()->setInt64("durationUs", durationUs);
+ buffer->meta()->setInt32("seq", mSeqNumber);
packetSource->queueAccessUnit(buffer);
return OK;
@@ -1044,6 +1231,7 @@
// Each AAC frame encodes 1024 samples.
numSamples += 1024;
+ unit->meta()->setInt32("seq", mSeqNumber);
packetSource->queueAccessUnit(unit);
offset += aac_frame_length;
@@ -1071,4 +1259,33 @@
msg->post();
}
+int64_t PlaylistFetcher::resumeThreshold(const sp<AMessage> &msg) {
+ int64_t durationUs, threshold;
+ if (msg->findInt64("durationUs", &durationUs)) {
+ return kNumSkipFrames * durationUs;
+ }
+
+ sp<RefBase> obj;
+ msg->findObject("format", &obj);
+ MetaData *format = static_cast<MetaData *>(obj.get());
+
+ const char *mime;
+ CHECK(format->findCString(kKeyMIMEType, &mime));
+ bool audio = !strncasecmp(mime, "audio/", 6);
+ if (audio) {
+ // Assumes 1000 samples per frame.
+ int32_t sampleRate;
+ CHECK(format->findInt32(kKeySampleRate, &sampleRate));
+ return kNumSkipFrames /* frames */ * 1000 /* samples */
+ * (1000000 / sampleRate) /* sample duration (us) */;
+ } else {
+ int32_t frameRate;
+ if (format->findInt32(kKeyFrameRate, &frameRate) && frameRate > 0) {
+ return kNumSkipFrames * (1000000 / frameRate);
+ }
+ }
+
+ return 500000ll;
+}
+
} // namespace android
diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h
index ac04a77..2e0349f 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.h
+++ b/media/libstagefright/httplive/PlaylistFetcher.h
@@ -43,6 +43,7 @@
kWhatTemporarilyDoneFetching,
kWhatPrepared,
kWhatPreparationFailed,
+ kWhatStartedAt,
};
PlaylistFetcher(
@@ -56,12 +57,16 @@
const sp<AnotherPacketSource> &audioSource,
const sp<AnotherPacketSource> &videoSource,
const sp<AnotherPacketSource> &subtitleSource,
- int64_t startTimeUs = -1ll);
+ int64_t startTimeUs = -1ll,
+ int64_t minStartTimeUs = 0ll /* start after this timestamp */,
+ int32_t startSeqNumberHint = -1 /* try starting at this sequence number */);
void pauseAsync();
void stopAsync();
+ void resumeUntilAsync(const sp<AMessage> ¶ms);
+
protected:
virtual ~PlaylistFetcher();
virtual void onMessageReceived(const sp<AMessage> &msg);
@@ -76,17 +81,25 @@
kWhatPause = 'paus',
kWhatStop = 'stop',
kWhatMonitorQueue = 'moni',
+ kWhatResumeUntil = 'rsme',
+ kWhatDownloadNext = 'dlnx',
};
static const int64_t kMinBufferedDurationUs;
static const int64_t kMaxMonitorDelayUs;
+ static const int32_t kNumSkipFrames;
+ // notifications to mSession
sp<AMessage> mNotify;
+ sp<AMessage> mStartTimeUsNotify;
+
sp<LiveSession> mSession;
AString mURI;
uint32_t mStreamTypeMask;
int64_t mStartTimeUs;
+ int64_t mMinStartTimeUs; // start fetching no earlier than this value
+ sp<AMessage> mStopParams; // message containing the latest timestamps we should fetch.
KeyedVector<LiveSession::StreamType, sp<AnotherPacketSource> >
mPacketSources;
@@ -153,6 +166,9 @@
void onMonitorQueue();
void onDownloadNext();
+ // Resume a fetcher to continue until the stopping point stored in msg.
+ status_t onResumeUntil(const sp<AMessage> &msg);
+
status_t extractAndQueueAccessUnits(
const sp<ABuffer> &buffer, const sp<AMessage> &itemMeta);
@@ -165,6 +181,10 @@
void updateDuration();
+ // Before resuming a fetcher in onResume, check the remaining duration is longer than that
+ // returned by resumeThreshold.
+ int64_t resumeThreshold(const sp<AMessage> &msg);
+
DISALLOW_EVIL_CONSTRUCTORS(PlaylistFetcher);
};
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 7615086..92ee30e 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -527,9 +527,24 @@
goto Exit;
}
+ // further sample rate checks are performed by createTrack_l() depending on the thread type
+ if (sampleRate == 0) {
+ ALOGE("createTrack() invalid sample rate %u", sampleRate);
+ lStatus = BAD_VALUE;
+ goto Exit;
+ }
+
+ // further channel mask checks are performed by createTrack_l() depending on the thread type
+ if (!audio_is_output_channel(channelMask)) {
+ ALOGE("createTrack() invalid channel mask %#x", channelMask);
+ lStatus = BAD_VALUE;
+ goto Exit;
+ }
+
// client is responsible for conversion of 8-bit PCM to 16-bit PCM,
// and we don't yet support 8.24 or 32-bit PCM
- if (audio_is_linear_pcm(format) && format != AUDIO_FORMAT_PCM_16_BIT) {
+ if (!audio_is_valid_format(format) ||
+ (audio_is_linear_pcm(format) && format != AUDIO_FORMAT_PCM_16_BIT)) {
ALOGE("createTrack() invalid format %#x", format);
lStatus = BAD_VALUE;
goto Exit;
@@ -1320,12 +1335,28 @@
goto Exit;
}
+ // further sample rate checks are performed by createRecordTrack_l()
+ if (sampleRate == 0) {
+ ALOGE("openRecord() invalid sample rate %u", sampleRate);
+ lStatus = BAD_VALUE;
+ goto Exit;
+ }
+
+ // FIXME when we support more formats, add audio_is_valid_format(format)
+ // and any explicit restrictions if audio_is_linear_pcm(format)
if (format != AUDIO_FORMAT_PCM_16_BIT) {
ALOGE("openRecord() invalid format %#x", format);
lStatus = BAD_VALUE;
goto Exit;
}
+ // further channel mask checks are performed by createRecordTrack_l()
+ if (!audio_is_input_channel(channelMask)) {
+ ALOGE("openRecord() invalid channel mask %#x", channelMask);
+ lStatus = BAD_VALUE;
+ goto Exit;
+ }
+
// add client to list
{ // scope for mLock
Mutex::Autolock _l(mLock);
@@ -1909,10 +1940,10 @@
return NO_ERROR;
}
-status_t AudioFlinger::setStreamOutput(audio_stream_type_t stream, audio_io_handle_t output)
+status_t AudioFlinger::invalidateStream(audio_stream_type_t stream)
{
Mutex::Autolock _l(mLock);
- ALOGV("setStreamOutput() stream %d to output %d", stream, output);
+ ALOGV("invalidateStream() stream %d", stream);
for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
PlaybackThread *thread = mPlaybackThreads.valueAt(i).get();
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 21d05d4..c2b516b 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -182,7 +182,7 @@
virtual status_t closeInput(audio_io_handle_t input);
- virtual status_t setStreamOutput(audio_stream_type_t stream, audio_io_handle_t output);
+ virtual status_t invalidateStream(audio_stream_type_t stream);
virtual status_t setVoiceVolume(float volume);
diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp
index 9980344..41bd990 100644
--- a/services/audioflinger/AudioPolicyService.cpp
+++ b/services/audioflinger/AudioPolicyService.cpp
@@ -1598,15 +1598,14 @@
return af->closeInput(input);
}
-static int aps_set_stream_output(void *service __unused, audio_stream_type_t stream,
- audio_io_handle_t output)
+static int aps_invalidate_stream(void *service __unused, audio_stream_type_t stream)
{
sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
if (af == 0) {
return PERMISSION_DENIED;
}
- return af->setStreamOutput(stream, output);
+ return af->invalidateStream(stream);
}
static int aps_move_effects(void *service __unused, int session,
@@ -1680,7 +1679,7 @@
.open_input = aps_open_input,
.close_input = aps_close_input,
.set_stream_volume = aps_set_stream_volume,
- .set_stream_output = aps_set_stream_output,
+ .invalidate_stream = aps_invalidate_stream,
.set_parameters = aps_set_parameters,
.get_parameters = aps_get_parameters,
.start_tone = aps_start_tone,