diff options
32 files changed, 1420 insertions, 214 deletions
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 @@ include $(CLEAR_VARS) 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 @@ using namespace android; 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 @@ status_t EglWindow::createWindow(const sp<IGraphicBufferProducer>& surface) { 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 @@ status_t EglWindow::makeCurrent() const { return NO_ERROR; } -status_t EglWindow::eglSetupContext() { +status_t EglWindow::eglSetupContext(bool forPbuffer) { EGLBoolean result; mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); @@ -82,17 +115,28 @@ status_t EglWindow::eglSetupContext() { 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 + }; + 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, configAttribs, &mEglConfig, 1, - &numConfigs); + 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 @@ public: // 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 @@ private: 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..6c37501 --- /dev/null +++ b/cmds/screenrecord/FrameOutput.cpp @@ -0,0 +1,211 @@ +/* + * 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, bool rawFrames) { + 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); + } + + size_t rgbDataLen = width * height * kOutBytesPerPixel; + + if (!rawFrames) { + // Fill out the header. + size_t headerLen = sizeof(uint32_t) * 5; + 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); + fwrite(header, 1, headerLen, fp); + } + + // 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(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..4ac3e8a --- /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, bool rawFrames); + + // 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::linkShaderProgram(GLuint vs, GLuint fs, GLuint* outPgm) { 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 Program::blit(GLuint texName, const float* texMatrix, }; 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 Program::drawTriangles(GLuint texName, const float* texMatrix, 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::drawTriangles(GLuint texName, const float* texMatrix, } 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 @@ status_t Program::beforeDraw(GLuint texName, const float* texMatrix, 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 @@ public: // 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 @@ private: // 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 61f83e3..02ed53a 100644 --- a/cmds/screenrecord/screenrecord.cpp +++ b/cmds/screenrecord/screenrecord.cpp @@ -45,10 +45,12 @@ #include <signal.h> #include <getopt.h> #include <sys/wait.h> +#include <termios.h> #include <assert.h> #include "screenrecord.h" #include "Overlay.h" +#include "FrameOutput.h" using namespace android; @@ -57,10 +59,14 @@ static const uint32_t kMaxBitRate = 200 * 1000000; // 200Mbps 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 enum { + FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES, FORMAT_RAW_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? @@ -140,14 +146,14 @@ static status_t prepareEncoder(float displayFps, sp<MediaCodec>* pCodec, 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); @@ -157,16 +163,18 @@ static status_t prepareEncoder(float displayFps, sp<MediaCodec>* pCodec, 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; } @@ -298,10 +306,12 @@ static status_t prepareVirtualDisplay(const DisplayInfo& mainDpyInfo, * input frames are coming from the virtual display as fast as SurfaceFlinger * wants to send them. * + * Exactly one of muxer or rawFp must be non-null. + * * The muxer must *not* have been started before calling. */ static status_t runEncoder(const sp<MediaCodec>& encoder, - const sp<MediaMuxer>& muxer, const sp<IBinder>& mainDpy, + const sp<MediaMuxer>& muxer, FILE* rawFp, const sp<IBinder>& mainDpy, const sp<IBinder>& virtualDpy, uint8_t orientation) { static int kTimeout = 250000; // be responsive on signal status_t err; @@ -311,6 +321,8 @@ static status_t runEncoder(const sp<MediaCodec>& encoder, int64_t endWhenNsec = startWhenNsec + seconds_to_nanoseconds(gTimeLimitSec); DisplayInfo mainDpyInfo; + assert((rawFp == NULL && muxer != NULL) || (rawFp != NULL && muxer == NULL)); + Vector<sp<ABuffer> > buffers; err = encoder->getOutputBuffers(&buffers); if (err != NO_ERROR) { @@ -342,15 +354,16 @@ static status_t runEncoder(const sp<MediaCodec>& encoder, case NO_ERROR: // got a buffer if ((flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) != 0) { - // ignore this -- we passed the CSD into MediaMuxer when - // we got the format change notification - ALOGV("Got codec config buffer (%u bytes); ignoring", size); - size = 0; + ALOGV("Got codec config buffer (%u bytes)", size); + if (muxer != NULL) { + // ignore this -- we passed the CSD into MediaMuxer when + // we got the format change notification + size = 0; + } } if (size != 0) { ALOGV("Got data in buffer %d, size=%d, pts=%lld", bufIndex, size, ptsUsec); - assert(trackIdx != -1); { // scope ATRACE_NAME("orientation"); @@ -379,14 +392,23 @@ static status_t runEncoder(const sp<MediaCodec>& encoder, ptsUsec = systemTime(SYSTEM_TIME_MONOTONIC) / 1000; } - // The MediaMuxer docs are unclear, but it appears that we - // need to pass either the full set of BufferInfo flags, or - // (flags & BUFFER_FLAG_SYNCFRAME). - // - // If this blocks for too long we could drop frames. We may - // want to queue these up and do them on a different thread. - { // scope + if (muxer == NULL) { + fwrite(buffers[bufIndex]->data(), 1, size, rawFp); + // Flush the data immediately in case we're streaming. + // We don't want to do this if all we've written is + // the SPS/PPS data because mplayer gets confused. + if ((flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) == 0) { + fflush(rawFp); + } + } else { + // The MediaMuxer docs are unclear, but it appears that we + // need to pass either the full set of BufferInfo flags, or + // (flags & BUFFER_FLAG_SYNCFRAME). + // + // If this blocks for too long we could drop frames. We may + // want to queue these up and do them on a different thread. ATRACE_NAME("write sample"); + assert(trackIdx != -1); err = muxer->writeSampleData(buffers[bufIndex], trackIdx, ptsUsec, flags); if (err != NO_ERROR) { @@ -418,12 +440,14 @@ static status_t runEncoder(const sp<MediaCodec>& encoder, ALOGV("Encoder format changed"); sp<AMessage> newFormat; encoder->getOutputFormat(&newFormat); - trackIdx = muxer->addTrack(newFormat); - ALOGV("Starting muxer"); - err = muxer->start(); - if (err != NO_ERROR) { - fprintf(stderr, "Unable to start muxer (err=%d)\n", err); - return err; + if (muxer != NULL) { + trackIdx = muxer->addTrack(newFormat); + ALOGV("Starting muxer"); + err = muxer->start(); + if (err != NO_ERROR) { + fprintf(stderr, "Unable to start muxer (err=%d)\n", err); + return err; + } } } break; @@ -457,7 +481,45 @@ static status_t runEncoder(const sp<MediaCodec>& encoder, } /* - * Main "do work" method. + * Raw H.264 byte stream output requested. Send the output to stdout + * if desired. If the output is a tty, reconfigure it to avoid the + * CRLF line termination that we see with "adb shell" commands. + */ +static FILE* prepareRawOutput(const char* fileName) { + FILE* rawFp = NULL; + + if (strcmp(fileName, "-") == 0) { + if (gVerbose) { + fprintf(stderr, "ERROR: verbose output and '-' not compatible"); + return NULL; + } + rawFp = stdout; + } else { + rawFp = fopen(fileName, "w"); + if (rawFp == NULL) { + fprintf(stderr, "fopen raw failed: %s\n", strerror(errno)); + return NULL; + } + } + + int fd = fileno(rawFp); + if (isatty(fd)) { + // best effort -- reconfigure tty for "raw" + ALOGD("raw video output to tty (fd=%d)", fd); + struct termios term; + if (tcgetattr(fd, &term) == 0) { + cfmakeraw(&term); + if (tcsetattr(fd, TCSANOW, &term) == 0) { + ALOGD("tty successfully configured for raw"); + } + } + } + + return rawFp; +} + +/* + * Main "do work" start point. * * Configures codec, muxer, and virtual display, then starts moving bits * around. @@ -499,30 +561,40 @@ static status_t recordScreen(const char* fileName) { // Configure and start the encoder. sp<MediaCodec> encoder; + sp<FrameOutput> frameOutput; sp<IGraphicBufferProducer> encoderInputSurface; - 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 (gOutputFormat != FORMAT_FRAMES && gOutputFormat != FORMAT_RAW_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) 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. + 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; + } + } // Draw the "info" page by rendering a frame with GLES and sending // it directly to the encoder. @@ -539,7 +611,7 @@ static status_t recordScreen(const char* fileName) { overlay = new Overlay(); err = overlay->start(encoderInputSurface, &bufferProducer); if (err != NO_ERROR) { - encoder->release(); + if (encoder != NULL) encoder->release(); return err; } if (gVerbose) { @@ -554,40 +626,93 @@ static status_t recordScreen(const char* fileName) { sp<IBinder> dpy; err = prepareVirtualDisplay(mainDpyInfo, bufferProducer, &dpy); if (err != NO_ERROR) { - encoder->release(); + if (encoder != NULL) encoder->release(); return err; } - // Configure muxer. We have to wait for the CSD blob from the encoder - // before we can start it. - sp<MediaMuxer> 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, mainDpy, dpy, mainDpyInfo.orientation); - if (err != NO_ERROR) { - fprintf(stderr, "Encoder failed (err=%d)\n", err); - // fall through to cleanup - } + sp<MediaMuxer> muxer = NULL; + FILE* rawFp = NULL; + 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: + case FORMAT_RAW_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 || gOutputFormat == FORMAT_RAW_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.) + bool rawFrames = gOutputFormat == FORMAT_RAW_FRAMES; + err = frameOutput->copyFrame(rawFp, 250000, rawFrames); + if (err == ETIMEDOUT) { + err = NO_ERROR; + } else if (err != NO_ERROR) { + ALOGE("Got error %d from copyFrame()", err); + break; + } + } + } else { + // 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(); + 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). + muxer->stop(); + } else if (rawFp != stdout) { + fclose(rawFp); } - encoder->stop(); - // If we don't stop muxer explicitly, i.e. let the destructor run, - // it may hang (b/11050628). - muxer->stop(); - encoder->release(); + if (encoder != NULL) encoder->release(); return err; } @@ -749,10 +874,12 @@ int main(int argc, char* const argv[]) { { "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' }, + { "output-format", required_argument, NULL, 'o' }, { NULL, 0, NULL, 0 } }; @@ -804,20 +931,34 @@ int main(int argc, char* const argv[]) { return 2; } break; - case 'i': + case 'u': gWantInfoScreen = true; - break; - case 'f': gWantFrameTime = true; break; - case 'u': + case 'i': gWantInfoScreen = true; + break; + case 'f': gWantFrameTime = true; break; case 'r': // experimental feature gRotate = true; break; + 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 if (strcmp(optarg, "raw-frames") == 0) { + gOutputFormat = FORMAT_RAW_FRAMES; + } else { + fprintf(stderr, "Unknown format '%s'\n", optarg); + return 2; + } + break; default: if (ic != '?') { fprintf(stderr, "getopt_long returned unexpected value 0x%x\n", ic); @@ -831,17 +972,19 @@ int main(int argc, char* const argv[]) { return 2; } - // 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 - // now for better diagnostics. const char* fileName = argv[optind]; - int fd = open(fileName, O_CREAT | O_RDWR, 0644); - if (fd < 0) { - fprintf(stderr, "Unable to open '%s': %s\n", fileName, strerror(errno)); - return 1; + 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 + // now for better diagnostics. + int fd = open(fileName, O_CREAT | O_RDWR, 0644); + if (fd < 0) { + fprintf(stderr, "Unable to open '%s': %s\n", fileName, strerror(errno)); + return 1; + } + close(fd); } - close(fd); status_t err = recordScreen(fileName); if (err == NO_ERROR) { diff --git a/cmds/screenrecord/screenrecord.h b/cmds/screenrecord/screenrecord.h index 95e8a68..9b058c2 100644 --- a/cmds/screenrecord/screenrecord.h +++ b/cmds/screenrecord/screenrecord.h @@ -18,6 +18,6 @@ #define SCREENRECORD_SCREENRECORD_H #define kVersionMajor 1 -#define kVersionMinor 1 +#define kVersionMinor 2 #endif /*SCREENRECORD_SCREENRECORD_H*/ diff --git a/drm/libdrmframework/Android.mk b/drm/libdrmframework/Android.mk index 49c4f9b..33f9d3b 100644 --- a/drm/libdrmframework/Android.mk +++ b/drm/libdrmframework/Android.mk @@ -19,12 +19,14 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ DrmManagerClientImpl.cpp \ - DrmManagerClient.cpp + DrmManagerClient.cpp \ + NoOpDrmManagerClientImpl.cpp LOCAL_MODULE:= libdrmframework LOCAL_SHARED_LIBRARIES := \ libutils \ + libcutils \ liblog \ libbinder \ libdl diff --git a/drm/libdrmframework/DrmManagerClient.cpp b/drm/libdrmframework/DrmManagerClient.cpp index ea30d01..440dd91 100644 --- a/drm/libdrmframework/DrmManagerClient.cpp +++ b/drm/libdrmframework/DrmManagerClient.cpp @@ -29,7 +29,7 @@ DrmManagerClient::DrmManagerClient(): } DrmManagerClient::~DrmManagerClient() { - DrmManagerClientImpl::remove(mUniqueId); + mDrmManagerClientImpl->remove(mUniqueId); mDrmManagerClientImpl->removeClient(mUniqueId); mDrmManagerClientImpl->setOnInfoListener(mUniqueId, NULL); } diff --git a/drm/libdrmframework/DrmManagerClientImpl.cpp b/drm/libdrmframework/DrmManagerClientImpl.cpp index ffefd74..2d2c90e 100644 --- a/drm/libdrmframework/DrmManagerClientImpl.cpp +++ b/drm/libdrmframework/DrmManagerClientImpl.cpp @@ -21,8 +21,10 @@ #include <utils/String8.h> #include <utils/Vector.h> #include <binder/IServiceManager.h> +#include <cutils/properties.h> #include "DrmManagerClientImpl.h" +#include "NoOpDrmManagerClientImpl.h" using namespace android; @@ -35,9 +37,12 @@ const String8 DrmManagerClientImpl::EMPTY_STRING(""); DrmManagerClientImpl* DrmManagerClientImpl::create( int* pUniqueId, bool isNative) { - *pUniqueId = getDrmManagerService()->addUniqueId(isNative); - - return new DrmManagerClientImpl(); + sp<IDrmManagerService> service = getDrmManagerService(); + if (service != NULL) { + *pUniqueId = getDrmManagerService()->addUniqueId(isNative); + return new DrmManagerClientImpl(); + } + return new NoOpDrmManagerClientImpl(); } void DrmManagerClientImpl::remove(int uniqueId) { @@ -47,6 +52,12 @@ void DrmManagerClientImpl::remove(int uniqueId) { const sp<IDrmManagerService>& DrmManagerClientImpl::getDrmManagerService() { Mutex::Autolock lock(sMutex); if (NULL == sDrmManagerService.get()) { + char value[PROPERTY_VALUE_MAX]; + if (property_get("drm.service.enabled", value, NULL) == 0) { + // Drm is undefined for this device + return sDrmManagerService; + } + sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder; do { diff --git a/drm/libdrmframework/NoOpDrmManagerClientImpl.cpp b/drm/libdrmframework/NoOpDrmManagerClientImpl.cpp new file mode 100644 index 0000000..dab583d --- /dev/null +++ b/drm/libdrmframework/NoOpDrmManagerClientImpl.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 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. + */ + +#include "NoOpDrmManagerClientImpl.h" + +namespace android { + +void NoOpDrmManagerClientImpl::remove(int uniqueId) { +} + +void NoOpDrmManagerClientImpl::addClient(int uniqueId) { +} + +void NoOpDrmManagerClientImpl::removeClient(int uniqueId) { +} + +status_t NoOpDrmManagerClientImpl::setOnInfoListener( + int uniqueId, const sp<DrmManagerClient::OnInfoListener>& infoListener) { + return UNKNOWN_ERROR; +} + +DrmConstraints* NoOpDrmManagerClientImpl::getConstraints(int uniqueId, const String8* path, const int action) { + return NULL; +} + +DrmMetadata* NoOpDrmManagerClientImpl::getMetadata(int uniqueId, const String8* path) { + return NULL; +} + +bool NoOpDrmManagerClientImpl::canHandle(int uniqueId, const String8& path, const String8& mimeType) { + return false; +} + +DrmInfoStatus* NoOpDrmManagerClientImpl::processDrmInfo(int uniqueId, const DrmInfo* drmInfo) { + return NULL; +} + +DrmInfo* NoOpDrmManagerClientImpl::acquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest) { + return NULL; +} + +status_t NoOpDrmManagerClientImpl::saveRights(int uniqueId, const DrmRights& drmRights, + const String8& rightsPath, const String8& contentPath) { + return UNKNOWN_ERROR; +} + +String8 NoOpDrmManagerClientImpl::getOriginalMimeType(int uniqueId, const String8& path, int fd) { + return String8(); +} + +int NoOpDrmManagerClientImpl::getDrmObjectType(int uniqueId, const String8& path, const String8& mimeType) { + return -1; +} + +int NoOpDrmManagerClientImpl::checkRightsStatus(int uniqueId, const String8& path, int action) { + return -1; +} + +status_t NoOpDrmManagerClientImpl::consumeRights(int uniqueId, sp<DecryptHandle> &decryptHandle, int action, bool reserve) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::setPlaybackStatus( + int uniqueId, sp<DecryptHandle> &decryptHandle, int playbackStatus, int64_t position) { + return UNKNOWN_ERROR; +} + +bool NoOpDrmManagerClientImpl::validateAction( + int uniqueId, const String8& path, int action, const ActionDescription& description) { + return false; +} + +status_t NoOpDrmManagerClientImpl::removeRights(int uniqueId, const String8& path) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::removeAllRights(int uniqueId) { + return UNKNOWN_ERROR; +} + +int NoOpDrmManagerClientImpl::openConvertSession(int uniqueId, const String8& mimeType) { + return -1; +} + +DrmConvertedStatus* NoOpDrmManagerClientImpl::convertData(int uniqueId, int convertId, const DrmBuffer* inputData) { + return NULL; +} + +DrmConvertedStatus* NoOpDrmManagerClientImpl::closeConvertSession(int uniqueId, int convertId) { + return NULL; +} + +status_t NoOpDrmManagerClientImpl::getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray) { + return UNKNOWN_ERROR; +} + +sp<DecryptHandle> NoOpDrmManagerClientImpl::openDecryptSession( + int uniqueId, int fd, off64_t offset, off64_t length, const char* mime) { + return NULL; +} + +sp<DecryptHandle> NoOpDrmManagerClientImpl::openDecryptSession( + int uniqueId, const char* uri, const char* mime) { + return NULL; +} + +sp<DecryptHandle> NoOpDrmManagerClientImpl::openDecryptSession(int uniqueId, const DrmBuffer& buf, + const String8& mimeType) { + return NULL; +} + +status_t NoOpDrmManagerClientImpl::closeDecryptSession(int uniqueId, sp<DecryptHandle> &decryptHandle) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::initializeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, + int decryptUnitId, const DrmBuffer* headerInfo) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::decrypt(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, + const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::finalizeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId) { + return UNKNOWN_ERROR; +} + +ssize_t NoOpDrmManagerClientImpl::pread(int uniqueId, sp<DecryptHandle> &decryptHandle, + void* buffer, ssize_t numBytes, off64_t offset) { + return -1; +} + +status_t NoOpDrmManagerClientImpl::notify(const DrmInfoEvent& event) { + return UNKNOWN_ERROR; +} + +} diff --git a/drm/libdrmframework/include/DrmManagerClientImpl.h b/drm/libdrmframework/include/DrmManagerClientImpl.h index 3400cb1..3858675 100644 --- a/drm/libdrmframework/include/DrmManagerClientImpl.h +++ b/drm/libdrmframework/include/DrmManagerClientImpl.h @@ -34,30 +34,30 @@ class DrmInfoEvent; * */ class DrmManagerClientImpl : public BnDrmServiceListener { -private: +protected: DrmManagerClientImpl() { } public: static DrmManagerClientImpl* create(int* pUniqueId, bool isNative); - static void remove(int uniqueId); - virtual ~DrmManagerClientImpl() { } public: + virtual void remove(int uniqueId); + /** * Adds the client respective to given unique id. * * @param[in] uniqueId Unique identifier for a session */ - void addClient(int uniqueId); + virtual void addClient(int uniqueId); /** * Removes the client respective to given unique id. * * @param[in] uniqueId Unique identifier for a session */ - void removeClient(int uniqueId); + virtual void removeClient(int uniqueId); /** * Register a callback to be invoked when the caller required to @@ -68,7 +68,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t setOnInfoListener( + virtual status_t setOnInfoListener( int uniqueId, const sp<DrmManagerClient::OnInfoListener>& infoListener); /** @@ -83,7 +83,7 @@ public: * @note * In case of error, return NULL */ - DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + virtual DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); /** * Get metadata information associated with input content. @@ -95,7 +95,7 @@ public: * @note * In case of error, return NULL */ - DrmMetadata* getMetadata(int uniqueId, const String8* path); + virtual DrmMetadata* getMetadata(int uniqueId, const String8* path); /** * Check whether the given mimetype or path can be handled @@ -106,7 +106,7 @@ public: * @return * True if DrmManager can handle given path or mime type. */ - bool canHandle(int uniqueId, const String8& path, const String8& mimeType); + virtual bool canHandle(int uniqueId, const String8& path, const String8& mimeType); /** * Executes given drm information based on its type @@ -116,7 +116,7 @@ public: * @return DrmInfoStatus * instance as a result of processing given input */ - DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); + virtual DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); /** * Retrieves necessary information for registration, unregistration or rights @@ -127,7 +127,7 @@ public: * @return DrmInfo * instance as a result of processing given input */ - DrmInfo* acquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest); + virtual DrmInfo* acquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest); /** * Save DRM rights to specified rights path @@ -140,7 +140,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t saveRights(int uniqueId, const DrmRights& drmRights, + virtual status_t saveRights(int uniqueId, const DrmRights& drmRights, const String8& rightsPath, const String8& contentPath); /** @@ -152,7 +152,7 @@ public: * @return String8 * Returns mime-type of the original content, such as "video/mpeg" */ - String8 getOriginalMimeType(int uniqueId, const String8& path, int fd); + virtual String8 getOriginalMimeType(int uniqueId, const String8& path, int fd); /** * Retrieves the type of the protected object (content, rights, etc..) @@ -165,7 +165,7 @@ public: * @return type of the DRM content, * such as DrmObjectType::CONTENT, DrmObjectType::RIGHTS_OBJECT */ - int getDrmObjectType(int uniqueId, const String8& path, const String8& mimeType); + virtual int getDrmObjectType(int uniqueId, const String8& path, const String8& mimeType); /** * Check whether the given content has valid rights or not @@ -176,7 +176,7 @@ public: * @return the status of the rights for the protected content, * such as RightsStatus::RIGHTS_VALID, RightsStatus::RIGHTS_EXPIRED, etc. */ - int checkRightsStatus(int uniqueId, const String8& path, int action); + virtual int checkRightsStatus(int uniqueId, const String8& path, int action); /** * Consumes the rights for a content. @@ -190,7 +190,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t consumeRights(int uniqueId, sp<DecryptHandle> &decryptHandle, int action, bool reserve); + virtual status_t consumeRights(int uniqueId, sp<DecryptHandle> &decryptHandle, int action, bool reserve); /** * Informs the DRM engine about the playback actions performed on the DRM files. @@ -203,7 +203,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t setPlaybackStatus( + virtual status_t setPlaybackStatus( int uniqueId, sp<DecryptHandle> &decryptHandle, int playbackStatus, int64_t position); /** @@ -215,7 +215,7 @@ public: * @param[in] description Detailed description of the action * @return true if the action is allowed. */ - bool validateAction( + virtual bool validateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); /** @@ -226,7 +226,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t removeRights(int uniqueId, const String8& path); + virtual status_t removeRights(int uniqueId, const String8& path); /** * Removes all the rights information of each plug-in associated with @@ -236,7 +236,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t removeAllRights(int uniqueId); + virtual status_t removeAllRights(int uniqueId); /** * This API is for Forward Lock based DRM scheme. @@ -248,7 +248,7 @@ public: * @param[in] mimeType Description/MIME type of the input data packet * @return Return handle for the convert session */ - int openConvertSession(int uniqueId, const String8& mimeType); + virtual int openConvertSession(int uniqueId, const String8& mimeType); /** * Accepts and converts the input data which is part of DRM file. @@ -263,7 +263,7 @@ public: * the output converted data and offset. In this case the * application will ignore the offset information. */ - DrmConvertedStatus* convertData(int uniqueId, int convertId, const DrmBuffer* inputData); + virtual DrmConvertedStatus* convertData(int uniqueId, int convertId, const DrmBuffer* inputData); /** * Informs the Drm Agent when there is no more data which need to be converted @@ -279,7 +279,7 @@ public: * the application on which offset these signature data * should be appended. */ - DrmConvertedStatus* closeConvertSession(int uniqueId, int convertId); + virtual DrmConvertedStatus* closeConvertSession(int uniqueId, int convertId); /** * Retrieves all DrmSupportInfo instance that native DRM framework can handle. @@ -292,7 +292,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); + virtual status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); /** * Open the decrypt session to decrypt the given protected content @@ -305,7 +305,7 @@ public: * @return * Handle for the decryption session */ - sp<DecryptHandle> openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char* mime); /** @@ -317,7 +317,7 @@ public: * @return * Handle for the decryption session */ - sp<DecryptHandle> openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, const char* uri, const char* mime); /** @@ -329,7 +329,7 @@ public: * @return * Handle for the decryption session */ - sp<DecryptHandle> openDecryptSession(int uniqueId, const DrmBuffer& buf, + virtual sp<DecryptHandle> openDecryptSession(int uniqueId, const DrmBuffer& buf, const String8& mimeType); /** @@ -340,7 +340,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t closeDecryptSession(int uniqueId, sp<DecryptHandle> &decryptHandle); + virtual status_t closeDecryptSession(int uniqueId, sp<DecryptHandle> &decryptHandle); /** * Initialize decryption for the given unit of the protected content @@ -352,7 +352,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, + virtual status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo); /** @@ -372,7 +372,7 @@ public: * DRM_ERROR_SESSION_NOT_OPENED, DRM_ERROR_DECRYPT_UNIT_NOT_INITIALIZED, * DRM_ERROR_DECRYPT for failure. */ - status_t decrypt(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, + virtual status_t decrypt(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); /** @@ -384,7 +384,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId); + virtual status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId); /** * Reads the specified number of bytes from an open DRM file. @@ -397,7 +397,7 @@ public: * * @return Number of bytes read. Returns -1 for Failure. */ - ssize_t pread(int uniqueId, sp<DecryptHandle> &decryptHandle, + virtual ssize_t pread(int uniqueId, sp<DecryptHandle> &decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); /** @@ -407,7 +407,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t notify(const DrmInfoEvent& event); + virtual status_t notify(const DrmInfoEvent& event); private: Mutex mLock; diff --git a/drm/libdrmframework/include/NoOpDrmManagerClientImpl.h b/drm/libdrmframework/include/NoOpDrmManagerClientImpl.h new file mode 100644 index 0000000..e8e8f42 --- /dev/null +++ b/drm/libdrmframework/include/NoOpDrmManagerClientImpl.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 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 __NO_OP_DRM_MANAGER_CLIENT_IMPL_H__ +#define __NO_OP_DRM_MANAGER_CLIENT_IMPL_H__ + +#include "DrmManagerClientImpl.h" + +namespace android { + +class NoOpDrmManagerClientImpl : public DrmManagerClientImpl { +public: + NoOpDrmManagerClientImpl() { } + + void remove(int uniqueId); + void addClient(int uniqueId); + void removeClient(int uniqueId); + status_t setOnInfoListener( + int uniqueId, const sp<DrmManagerClient::OnInfoListener>& infoListener); + DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + + DrmMetadata* getMetadata(int uniqueId, const String8* path); + bool canHandle(int uniqueId, const String8& path, const String8& mimeType); + DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); + DrmInfo* acquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest); + status_t saveRights(int uniqueId, const DrmRights& drmRights, + const String8& rightsPath, const String8& contentPath); + String8 getOriginalMimeType(int uniqueId, const String8& path, int fd); + int getDrmObjectType(int uniqueId, const String8& path, const String8& mimeType); + int checkRightsStatus(int uniqueId, const String8& path, int action); + status_t consumeRights(int uniqueId, sp<DecryptHandle> &decryptHandle, int action, bool reserve); + status_t setPlaybackStatus( + int uniqueId, sp<DecryptHandle> &decryptHandle, int playbackStatus, int64_t position); + bool validateAction( + int uniqueId, const String8& path, int action, const ActionDescription& description); + status_t removeRights(int uniqueId, const String8& path); + status_t removeAllRights(int uniqueId); + int openConvertSession(int uniqueId, const String8& mimeType); + DrmConvertedStatus* convertData(int uniqueId, int convertId, const DrmBuffer* inputData); + DrmConvertedStatus* closeConvertSession(int uniqueId, int convertId); + status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); + sp<DecryptHandle> openDecryptSession( + int uniqueId, int fd, off64_t offset, off64_t length, const char* mime); + sp<DecryptHandle> openDecryptSession( + int uniqueId, const char* uri, const char* mime); + sp<DecryptHandle> openDecryptSession(int uniqueId, const DrmBuffer& buf, + const String8& mimeType); + status_t closeDecryptSession(int uniqueId, sp<DecryptHandle> &decryptHandle); + status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, + int decryptUnitId, const DrmBuffer* headerInfo); + status_t decrypt(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, + const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); + status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId); + ssize_t pread(int uniqueId, sp<DecryptHandle> &decryptHandle, + void* buffer, ssize_t numBytes, off64_t offset); + status_t notify(const DrmInfoEvent& event); +}; + +} + +#endif // __NO_OP_DRM_MANAGER_CLIENT_IMPL_H diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp index 69fa7a0..6efc712 100644 --- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp +++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp @@ -45,7 +45,7 @@ namespace android { // MockDrmFactory bool MockDrmFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) { - return (!memcmp(uuid, mock_uuid, sizeof(uuid))); + return (!memcmp(uuid, mock_uuid, sizeof(mock_uuid))); } bool MockDrmFactory::isContentTypeSupported(const String8 &mimeType) @@ -65,7 +65,7 @@ namespace android { // MockCryptoFactory bool MockCryptoFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) const { - return (!memcmp(uuid, mock_uuid, sizeof(uuid))); + return (!memcmp(uuid, mock_uuid, sizeof(mock_uuid))); } status_t MockCryptoFactory::createPlugin(const uint8_t uuid[16], const void *data, @@ -254,7 +254,9 @@ namespace android { return OK; } - status_t MockDrmPlugin::getProvisionRequest(Vector<uint8_t> &request, + status_t MockDrmPlugin::getProvisionRequest(String8 const &certType, + String8 const &certAuthority, + Vector<uint8_t> &request, String8 &defaultUrl) { Mutex::Autolock lock(mLock); @@ -282,7 +284,9 @@ namespace android { return OK; } - status_t MockDrmPlugin::provideProvisionResponse(Vector<uint8_t> const &response) + status_t MockDrmPlugin::provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey) { Mutex::Autolock lock(mLock); ALOGD("MockDrmPlugin::provideProvisionResponse(%s)", @@ -600,6 +604,33 @@ namespace android { return OK; } + status_t MockDrmPlugin::signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature) + { + Mutex::Autolock lock(mLock); + ALOGD("MockDrmPlugin::signRSA(sessionId=%s, algorithm=%s, keyId=%s, " + "message=%s, signature=%s)", + vectorToString(sessionId).string(), + algorithm.string(), + vectorToString(message).string(), + vectorToString(wrappedKey).string(), + vectorToString(signature).string()); + + // Properties used in mock test, set by mock plugin and verifed cts test app + // byte[] wrappedKey -> mock-wrappedkey + // byte[] message -> mock-message + // byte[] signature -> mock-signature + mByteArrayProperties.add(String8("mock-sessionid"), sessionId); + mStringProperties.add(String8("mock-algorithm"), algorithm); + mByteArrayProperties.add(String8("mock-message"), message); + mByteArrayProperties.add(String8("mock-wrappedkey"), wrappedKey); + mByteArrayProperties.add(String8("mock-signature"), signature); + return OK; + } + ssize_t MockDrmPlugin::findSession(Vector<uint8_t> const &sessionId) const { ALOGD("findSession: nsessions=%d, size=%d", mSessions.size(), sessionId.size()); diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h index 2297f9b..97d7052 100644 --- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h +++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h @@ -76,10 +76,14 @@ namespace android { status_t queryKeyStatus(Vector<uint8_t> const &sessionId, KeyedVector<String8, String8> &infoMap) const; - status_t getProvisionRequest(Vector<uint8_t> &request, - String8 &defaultUrl); + status_t getProvisionRequest(String8 const &certType, + String8 const &certAuthority, + Vector<uint8_t> &request, + String8 &defaultUrl); - status_t provideProvisionResponse(Vector<uint8_t> const &response); + status_t provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey); status_t getSecureStops(List<Vector<uint8_t> > &secureStops); status_t releaseSecureStops(Vector<uint8_t> const &ssRelease); @@ -122,6 +126,12 @@ namespace android { Vector<uint8_t> const &signature, bool &match); + status_t signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature); + private: String8 vectorToString(Vector<uint8_t> const &vector) const; String8 arrayToString(uint8_t const *array, size_t len) const; diff --git a/include/media/IDrm.h b/include/media/IDrm.h index 5ef26af..32ae28e 100644 --- a/include/media/IDrm.h +++ b/include/media/IDrm.h @@ -61,10 +61,14 @@ struct IDrm : public IInterface { virtual status_t queryKeyStatus(Vector<uint8_t> const &sessionId, KeyedVector<String8, String8> &infoMap) const = 0; - virtual status_t getProvisionRequest(Vector<uint8_t> &request, + virtual status_t getProvisionRequest(String8 const &certType, + String8 const &certAuthority, + Vector<uint8_t> &request, String8 &defaulUrl) = 0; - virtual status_t provideProvisionResponse(Vector<uint8_t> const &response) = 0; + virtual status_t provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey) = 0; virtual status_t getSecureStops(List<Vector<uint8_t> > &secureStops) = 0; @@ -107,6 +111,12 @@ struct IDrm : public IInterface { Vector<uint8_t> const &signature, bool &match) = 0; + virtual status_t signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature) = 0; + virtual status_t setListener(const sp<IDrmClient>& listener) = 0; private: diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h index 46c62dc..7ba5acc 100644 --- a/include/media/stagefright/ACodec.h +++ b/include/media/stagefright/ACodec.h @@ -203,7 +203,6 @@ private: unsigned mDequeueCounter; bool mStoreMetaDataInOutputBuffers; int32_t mMetaDataBuffersToSubmit; - size_t mNumUndequeuedBuffers; int64_t mRepeatFrameDelayUs; int64_t mMaxPtsGapUs; @@ -254,6 +253,8 @@ private: int32_t numChannels, int32_t sampleRate, int32_t bitRate, int32_t aacProfile, bool isADTS); + status_t setupAC3Codec(bool encoder, int32_t numChannels, int32_t sampleRate); + status_t selectAudioPortFormat( OMX_U32 portIndex, OMX_AUDIO_CODINGTYPE desiredFormat); diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h index 85693d4..cf5beda 100644 --- a/include/media/stagefright/MediaDefs.h +++ b/include/media/stagefright/MediaDefs.h @@ -44,6 +44,7 @@ extern const char *MEDIA_MIMETYPE_AUDIO_RAW; extern const char *MEDIA_MIMETYPE_AUDIO_FLAC; extern const char *MEDIA_MIMETYPE_AUDIO_AAC_ADTS; extern const char *MEDIA_MIMETYPE_AUDIO_MSGSM; +extern const char *MEDIA_MIMETYPE_AUDIO_AC3; extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG4; extern const char *MEDIA_MIMETYPE_CONTAINER_WAV; diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h index daaf20f..5121c17 100644 --- a/include/media/stagefright/OMXCodec.h +++ b/include/media/stagefright/OMXCodec.h @@ -248,6 +248,8 @@ private: int32_t numChannels, int32_t sampleRate, int32_t bitRate, int32_t aacProfile, bool isADTS); + status_t setAC3Format(int32_t numChannels, int32_t sampleRate); + void setG711Format(int32_t numChannels); status_t setVideoPortFormatType( diff --git a/media/libmedia/IDrm.cpp b/media/libmedia/IDrm.cpp index f7a9a75..f1a6a9f 100644 --- a/media/libmedia/IDrm.cpp +++ b/media/libmedia/IDrm.cpp @@ -51,6 +51,7 @@ enum { ENCRYPT, DECRYPT, SIGN, + SIGN_RSA, VERIFY, SET_LISTENER }; @@ -196,11 +197,15 @@ struct BpDrm : public BpInterface<IDrm> { return reply.readInt32(); } - virtual status_t getProvisionRequest(Vector<uint8_t> &request, + virtual status_t getProvisionRequest(String8 const &certType, + String8 const &certAuthority, + Vector<uint8_t> &request, String8 &defaultUrl) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + data.writeString8(certType); + data.writeString8(certAuthority); remote()->transact(GET_PROVISION_REQUEST, data, &reply); readVector(reply, request); @@ -209,13 +214,18 @@ struct BpDrm : public BpInterface<IDrm> { return reply.readInt32(); } - virtual status_t provideProvisionResponse(Vector<uint8_t> const &response) { + virtual status_t provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); writeVector(data, response); remote()->transact(PROVIDE_PROVISION_RESPONSE, data, &reply); + readVector(reply, certificate); + readVector(reply, wrappedKey); + return reply.readInt32(); } @@ -386,6 +396,25 @@ struct BpDrm : public BpInterface<IDrm> { return reply.readInt32(); } + virtual status_t signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature) { + Parcel data, reply; + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + writeVector(data, sessionId); + data.writeString8(algorithm); + writeVector(data, message); + writeVector(data, wrappedKey); + + remote()->transact(SIGN_RSA, data, &reply); + readVector(reply, signature); + + return reply.readInt32(); + } + virtual status_t setListener(const sp<IDrmClient>& listener) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); @@ -563,9 +592,13 @@ status_t BnDrm::onTransact( case GET_PROVISION_REQUEST: { CHECK_INTERFACE(IDrm, data, reply); + String8 certType = data.readString8(); + String8 certAuthority = data.readString8(); + Vector<uint8_t> request; String8 defaultUrl; - status_t result = getProvisionRequest(request, defaultUrl); + status_t result = getProvisionRequest(certType, certAuthority, + request, defaultUrl); writeVector(reply, request); reply->writeString8(defaultUrl); reply->writeInt32(result); @@ -576,8 +609,13 @@ status_t BnDrm::onTransact( { CHECK_INTERFACE(IDrm, data, reply); Vector<uint8_t> response; + Vector<uint8_t> certificate; + Vector<uint8_t> wrappedKey; readVector(data, response); - reply->writeInt32(provideProvisionResponse(response)); + status_t result = provideProvisionResponse(response, certificate, wrappedKey); + writeVector(reply, certificate); + writeVector(reply, wrappedKey); + reply->writeInt32(result); return OK; } @@ -725,6 +763,20 @@ status_t BnDrm::onTransact( return OK; } + case SIGN_RSA: + { + CHECK_INTERFACE(IDrm, data, reply); + Vector<uint8_t> sessionId, message, wrappedKey, signature; + readVector(data, sessionId); + String8 algorithm = data.readString8(); + readVector(data, message); + readVector(data, wrappedKey); + uint32_t result = signRSA(sessionId, algorithm, message, wrappedKey, signature); + writeVector(reply, signature); + reply->writeInt32(result); + return OK; + } + case SET_LISTENER: { CHECK_INTERFACE(IDrm, data, reply); sp<IDrmClient> listener = diff --git a/media/libmediaplayerservice/Drm.cpp b/media/libmediaplayerservice/Drm.cpp index eebcb79..d50037f 100644 --- a/media/libmediaplayerservice/Drm.cpp +++ b/media/libmediaplayerservice/Drm.cpp @@ -28,9 +28,21 @@ #include <media/stagefright/foundation/AString.h> #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/MediaErrors.h> +#include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> namespace android { +static bool checkPermission(const char* permissionString) { +#ifndef HAVE_ANDROID_OS + return true; +#endif + if (getpid() == IPCThreadState::self()->getCallingPid()) return true; + bool ok = checkCallingPermission(String16(permissionString)); + if (!ok) ALOGE("Request requires %s", permissionString); + return ok; +} + KeyedVector<Vector<uint8_t>, String8> Drm::mUUIDToLibraryPathMap; KeyedVector<String8, wp<SharedLibrary> > Drm::mLibraryPathToOpenLibraryMap; Mutex Drm::mMapLock; @@ -373,7 +385,8 @@ status_t Drm::queryKeyStatus(Vector<uint8_t> const &sessionId, return mPlugin->queryKeyStatus(sessionId, infoMap); } -status_t Drm::getProvisionRequest(Vector<uint8_t> &request, String8 &defaultUrl) { +status_t Drm::getProvisionRequest(String8 const &certType, String8 const &certAuthority, + Vector<uint8_t> &request, String8 &defaultUrl) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { @@ -384,10 +397,13 @@ status_t Drm::getProvisionRequest(Vector<uint8_t> &request, String8 &defaultUrl) return -EINVAL; } - return mPlugin->getProvisionRequest(request, defaultUrl); + return mPlugin->getProvisionRequest(certType, certAuthority, + request, defaultUrl); } -status_t Drm::provideProvisionResponse(Vector<uint8_t> const &response) { +status_t Drm::provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { @@ -398,7 +414,7 @@ status_t Drm::provideProvisionResponse(Vector<uint8_t> const &response) { return -EINVAL; } - return mPlugin->provideProvisionResponse(response); + return mPlugin->provideProvisionResponse(response, certificate, wrappedKey); } @@ -589,6 +605,28 @@ status_t Drm::verify(Vector<uint8_t> const &sessionId, return mPlugin->verify(sessionId, keyId, message, signature, match); } +status_t Drm::signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature) { + Mutex::Autolock autoLock(mLock); + + if (mInitCheck != OK) { + return mInitCheck; + } + + if (mPlugin == NULL) { + return -EINVAL; + } + + if (!checkPermission("android.permission.ACCESS_DRM_CERTIFICATES")) { + return -EPERM; + } + + return mPlugin->signRSA(sessionId, algorithm, message, wrappedKey, signature); +} + void Drm::binderDied(const wp<IBinder> &the_late_who) { delete mPlugin; diff --git a/media/libmediaplayerservice/Drm.h b/media/libmediaplayerservice/Drm.h index 119fd50..3d4b0fc 100644 --- a/media/libmediaplayerservice/Drm.h +++ b/media/libmediaplayerservice/Drm.h @@ -66,10 +66,14 @@ struct Drm : public BnDrm, virtual status_t queryKeyStatus(Vector<uint8_t> const &sessionId, KeyedVector<String8, String8> &infoMap) const; - virtual status_t getProvisionRequest(Vector<uint8_t> &request, + virtual status_t getProvisionRequest(String8 const &certType, + String8 const &certAuthority, + Vector<uint8_t> &request, String8 &defaulUrl); - virtual status_t provideProvisionResponse(Vector<uint8_t> const &response); + virtual status_t provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey); virtual status_t getSecureStops(List<Vector<uint8_t> > &secureStops); @@ -111,6 +115,12 @@ struct Drm : public BnDrm, Vector<uint8_t> const &signature, bool &match); + virtual status_t signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature); + virtual status_t setListener(const sp<IDrmClient>& listener); virtual void sendEvent(DrmPlugin::EventType eventType, int extra, diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index ed1de58..f2d960a 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -38,7 +38,9 @@ #include <media/hardware/HardwareAPI.h> +#include <OMX_AudioExt.h> #include <OMX_Component.h> +#include <OMX_IndexExt.h> #include "include/avc_utils.h" @@ -641,21 +643,11 @@ status_t ACodec::configureOutputBuffersFromNativeWindow( return err; } - // FIXME: assume that surface is controlled by app (native window - // returns the number for the case when surface is not controlled by app) - // FIXME2: This means that minUndeqeueudBufs can be 1 larger than reported - // For now, try to allocate 1 more buffer, but don't fail if unsuccessful - - // Use conservative allocation while also trying to reduce starvation - // - // 1. allocate at least nBufferCountMin + minUndequeuedBuffers - that is the - // minimum needed for the consumer to be able to work - // 2. try to allocate two (2) additional buffers to reduce starvation from - // the consumer - // plus an extra buffer to account for incorrect minUndequeuedBufs - for (OMX_U32 extraBuffers = 2 + 1; /* condition inside loop */; extraBuffers--) { - OMX_U32 newBufferCount = - def.nBufferCountMin + *minUndequeuedBuffers + extraBuffers; + // XXX: Is this the right logic to use? It's not clear to me what the OMX + // buffer counts refer to - how do they account for the renderer holding on + // to buffers? + if (def.nBufferCountActual < def.nBufferCountMin + *minUndequeuedBuffers) { + OMX_U32 newBufferCount = def.nBufferCountMin + *minUndequeuedBuffers; def.nBufferCountActual = newBufferCount; err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); @@ -693,7 +685,6 @@ status_t ACodec::allocateOutputBuffersFromNativeWindow() { &bufferCount, &bufferSize, &minUndequeuedBuffers); if (err != 0) return err; - mNumUndequeuedBuffers = minUndequeuedBuffers; ALOGV("[%s] Allocating %u buffers from a native window of size %u on " "output port", @@ -759,7 +750,6 @@ status_t ACodec::allocateOutputMetaDataBuffers() { &bufferCount, &bufferSize, &minUndequeuedBuffers); if (err != 0) return err; - mNumUndequeuedBuffers = minUndequeuedBuffers; ALOGV("[%s] Allocating %u meta buffers on output port", mComponentName.c_str(), bufferCount); @@ -1000,6 +990,10 @@ status_t ACodec::setComponentRole( "audio_decoder.flac", "audio_encoder.flac" }, { MEDIA_MIMETYPE_AUDIO_MSGSM, "audio_decoder.gsm", "audio_encoder.gsm" }, + { MEDIA_MIMETYPE_VIDEO_MPEG2, + "video_decoder.mpeg2", "video_encoder.mpeg2" }, + { MEDIA_MIMETYPE_AUDIO_AC3, + "audio_decoder.ac3", "audio_encoder.ac3" }, }; static const size_t kNumMimeToRole = @@ -1298,6 +1292,15 @@ status_t ACodec::configureCodec( } else { err = setupRawAudioFormat(kPortIndexInput, sampleRate, numChannels); } + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AC3)) { + int32_t numChannels; + int32_t sampleRate; + if (!msg->findInt32("channel-count", &numChannels) + || !msg->findInt32("sample-rate", &sampleRate)) { + err = INVALID_OPERATION; + } else { + err = setupAC3Codec(encoder, numChannels, sampleRate); + } } if (err != OK) { @@ -1494,6 +1497,44 @@ status_t ACodec::setupAACCodec( mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); } +status_t ACodec::setupAC3Codec( + bool encoder, int32_t numChannels, int32_t sampleRate) { + status_t err = setupRawAudioFormat( + encoder ? kPortIndexInput : kPortIndexOutput, sampleRate, numChannels); + + if (err != OK) { + return err; + } + + if (encoder) { + ALOGW("AC3 encoding is not supported."); + return INVALID_OPERATION; + } + + OMX_AUDIO_PARAM_ANDROID_AC3TYPE def; + InitOMXParams(&def); + def.nPortIndex = kPortIndexInput; + + err = mOMX->getParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAc3, + &def, + sizeof(def)); + + if (err != OK) { + return err; + } + + def.nChannels = numChannels; + def.nSampleRate = sampleRate; + + return mOMX->setParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAc3, + &def, + sizeof(def)); +} + static OMX_AUDIO_AMRBANDMODETYPE pickModeFromBitRate( bool isAMRWB, int32_t bps) { if (isAMRWB) { @@ -2450,7 +2491,19 @@ void ACodec::waitUntilAllPossibleNativeWindowBuffersAreReturnedToUs() { return; } - while (countBuffersOwnedByNativeWindow() > mNumUndequeuedBuffers + int minUndequeuedBufs = 0; + status_t err = mNativeWindow->query( + mNativeWindow.get(), NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, + &minUndequeuedBufs); + + if (err != OK) { + ALOGE("[%s] NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS query failed: %s (%d)", + mComponentName.c_str(), strerror(-err), -err); + + minUndequeuedBufs = 0; + } + + while (countBuffersOwnedByNativeWindow() > (size_t)minUndequeuedBufs && dequeueBufferFromNativeWindow() != NULL) { // these buffers will be submitted as regular buffers; account for this if (mStoreMetaDataInOutputBuffers && mMetaDataBuffersToSubmit > 0) { @@ -2576,7 +2629,7 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { { OMX_AUDIO_PORTDEFINITIONTYPE *audioDef = &def.format.audio; - switch (audioDef->eEncoding) { + switch ((int)audioDef->eEncoding) { case OMX_AUDIO_CodingPCM: { OMX_AUDIO_PARAM_PCMMODETYPE params; @@ -2682,6 +2735,24 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { break; } + case OMX_AUDIO_CodingAndroidAC3: + { + OMX_AUDIO_PARAM_ANDROID_AC3TYPE params; + InitOMXParams(¶ms); + params.nPortIndex = kPortIndexOutput; + + CHECK_EQ((status_t)OK, mOMX->getParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAc3, + ¶ms, + sizeof(params))); + + notify->setString("mime", MEDIA_MIMETYPE_AUDIO_AC3); + notify->setInt32("channel-count", params.nChannels); + notify->setInt32("sample-rate", params.nSampleRate); + break; + } + default: TRESPASS(); } diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp index 6248e90..b74b2e2 100644 --- a/media/libstagefright/MediaCodecList.cpp +++ b/media/libstagefright/MediaCodecList.cpp @@ -57,15 +57,6 @@ MediaCodecList::MediaCodecList() parseXMLFile(file); - if (mInitCheck == OK) { - // These are currently still used by the video editing suite. - - addMediaCodec(true /* encoder */, "AACEncoder", "audio/mp4a-latm"); - - addMediaCodec( - false /* encoder */, "OMX.google.raw.decoder", "audio/raw"); - } - #if 0 for (size_t i = 0; i < mCodecInfos.size(); ++i) { const CodecInfo &info = mCodecInfos.itemAt(i); diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp index b5d4e44..340cba7 100644 --- a/media/libstagefright/MediaDefs.cpp +++ b/media/libstagefright/MediaDefs.cpp @@ -42,6 +42,7 @@ const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw"; const char *MEDIA_MIMETYPE_AUDIO_FLAC = "audio/flac"; const char *MEDIA_MIMETYPE_AUDIO_AAC_ADTS = "audio/aac-adts"; const char *MEDIA_MIMETYPE_AUDIO_MSGSM = "audio/gsm"; +const char *MEDIA_MIMETYPE_AUDIO_AC3 = "audio/ac3"; const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mp4"; const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/x-wav"; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index d5550e8..97bf514 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -42,7 +42,9 @@ #include <utils/Vector.h> #include <OMX_Audio.h> +#include <OMX_AudioExt.h> #include <OMX_Component.h> +#include <OMX_IndexExt.h> #include "include/avc_utils.h" @@ -94,7 +96,6 @@ static sp<MediaSource> InstantiateSoftwareEncoder( #define CODEC_LOGI(x, ...) ALOGI("[%s] "x, mComponentName, ##__VA_ARGS__) #define CODEC_LOGV(x, ...) ALOGV("[%s] "x, mComponentName, ##__VA_ARGS__) -#define CODEC_LOGW(x, ...) ALOGW("[%s] "x, mComponentName, ##__VA_ARGS__) #define CODEC_LOGE(x, ...) ALOGE("[%s] "x, mComponentName, ##__VA_ARGS__) struct OMXCodecObserver : public BnOMXObserver { @@ -531,6 +532,17 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { sampleRate, numChannels); } + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AC3, mMIME)) { + int32_t numChannels; + int32_t sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + + status_t err = setAC3Format(numChannels, sampleRate); + if (err != OK) { + CODEC_LOGE("setAC3Format() failed (err = %d)", err); + return err; + } } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_G711_ALAW, mMIME) || !strcasecmp(MEDIA_MIMETYPE_AUDIO_G711_MLAW, mMIME)) { // These are PCM-like formats with a fixed sample rate but @@ -1397,6 +1409,10 @@ void OMXCodec::setComponentRole( "audio_decoder.flac", "audio_encoder.flac" }, { MEDIA_MIMETYPE_AUDIO_MSGSM, "audio_decoder.gsm", "audio_encoder.gsm" }, + { MEDIA_MIMETYPE_VIDEO_MPEG2, + "video_decoder.mpeg2", "video_encoder.mpeg2" }, + { MEDIA_MIMETYPE_AUDIO_AC3, + "audio_decoder.ac3", "audio_encoder.ac3" }, }; static const size_t kNumMimeToRole = @@ -1780,24 +1796,12 @@ status_t OMXCodec::allocateOutputBuffersFromNativeWindow() { strerror(-err), -err); return err; } - // FIXME: assume that surface is controlled by app (native window - // returns the number for the case when surface is not controlled by app) - // FIXME2: This means that minUndeqeueudBufs can be 1 larger than reported - // For now, try to allocate 1 more buffer, but don't fail if unsuccessful - - // Use conservative allocation while also trying to reduce starvation - // - // 1. allocate at least nBufferCountMin + minUndequeuedBuffers - that is the - // minimum needed for the consumer to be able to work - // 2. try to allocate two (2) additional buffers to reduce starvation from - // the consumer - // plus an extra buffer to account for incorrect minUndequeuedBufs - CODEC_LOGI("OMX-buffers: min=%u actual=%u undeq=%d+1", - def.nBufferCountMin, def.nBufferCountActual, minUndequeuedBufs); - - for (OMX_U32 extraBuffers = 2 + 1; /* condition inside loop */; extraBuffers--) { - OMX_U32 newBufferCount = - def.nBufferCountMin + minUndequeuedBufs + extraBuffers; + + // XXX: Is this the right logic to use? It's not clear to me what the OMX + // buffer counts refer to - how do they account for the renderer holding on + // to buffers? + if (def.nBufferCountActual < def.nBufferCountMin + minUndequeuedBufs) { + OMX_U32 newBufferCount = def.nBufferCountMin + minUndequeuedBufs; def.nBufferCountActual = newBufferCount; err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); @@ -1814,8 +1818,6 @@ status_t OMXCodec::allocateOutputBuffersFromNativeWindow() { return err; } } - CODEC_LOGI("OMX-buffers: min=%u actual=%u undeq=%d+1", - def.nBufferCountMin, def.nBufferCountActual, minUndequeuedBufs); err = native_window_set_buffer_count( mNativeWindow.get(), def.nBufferCountActual); @@ -3513,6 +3515,31 @@ status_t OMXCodec::setAACFormat( return OK; } +status_t OMXCodec::setAC3Format(int32_t numChannels, int32_t sampleRate) { + OMX_AUDIO_PARAM_ANDROID_AC3TYPE def; + InitOMXParams(&def); + def.nPortIndex = kPortIndexInput; + + status_t err = mOMX->getParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAc3, + &def, + sizeof(def)); + + if (err != OK) { + return err; + } + + def.nChannels = numChannels; + def.nSampleRate = sampleRate; + + return mOMX->setParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAc3, + &def, + sizeof(def)); +} + void OMXCodec::setG711Format(int32_t numChannels) { CHECK(!mIsEncoder); setRawAudioFormat(kPortIndexInput, 8000, numChannels); @@ -4446,6 +4473,17 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { mOutputFormat->setInt32(kKeyChannelCount, numChannels); mOutputFormat->setInt32(kKeySampleRate, sampleRate); mOutputFormat->setInt32(kKeyBitRate, bitRate); + } else if (audio_def->eEncoding == + (OMX_AUDIO_CODINGTYPE)OMX_AUDIO_CodingAndroidAC3) { + mOutputFormat->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AC3); + int32_t numChannels, sampleRate, bitRate; + inputFormat->findInt32(kKeyChannelCount, &numChannels); + inputFormat->findInt32(kKeySampleRate, &sampleRate); + inputFormat->findInt32(kKeyBitRate, &bitRate); + mOutputFormat->setInt32(kKeyChannelCount, numChannels); + mOutputFormat->setInt32(kKeySampleRate, sampleRate); + mOutputFormat->setInt32(kKeyBitRate, bitRate); } else { CHECK(!"Should not be here. Unknown audio encoding."); } diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp index 2c8cf8d..d1afd8b 100644 --- a/media/libstagefright/mpeg2ts/ATSParser.cpp +++ b/media/libstagefright/mpeg2ts/ATSParser.cpp @@ -508,6 +508,11 @@ ATSParser::Stream::Stream( ElementaryStreamQueue::PCM_AUDIO); break; + case STREAMTYPE_AC3: + mQueue = new ElementaryStreamQueue( + ElementaryStreamQueue::AC3); + break; + default: break; } @@ -616,6 +621,7 @@ bool ATSParser::Stream::isAudio() const { case STREAMTYPE_MPEG2_AUDIO: case STREAMTYPE_MPEG2_AUDIO_ADTS: case STREAMTYPE_PCM_AUDIO: + case STREAMTYPE_AC3: return true; default: diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h index 8a80069..86b025f 100644 --- a/media/libstagefright/mpeg2ts/ATSParser.h +++ b/media/libstagefright/mpeg2ts/ATSParser.h @@ -89,6 +89,10 @@ struct ATSParser : public RefBase { STREAMTYPE_MPEG2_AUDIO_ADTS = 0x0f, STREAMTYPE_MPEG4_VIDEO = 0x10, STREAMTYPE_H264 = 0x1b, + + // From ATSC A/53 Part 3:2009, 6.7.1 + STREAMTYPE_AC3 = 0x81, + STREAMTYPE_PCM_AUDIO = 0x83, }; diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp index 1960b27..e9252cc 100644 --- a/media/libstagefright/mpeg2ts/ESQueue.cpp +++ b/media/libstagefright/mpeg2ts/ESQueue.cpp @@ -57,6 +57,122 @@ void ElementaryStreamQueue::clear(bool clearFormat) { } } +// Parse AC3 header assuming the current ptr is start position of syncframe, +// update metadata only applicable, and return the payload size +static unsigned parseAC3SyncFrame( + const uint8_t *ptr, size_t size, sp<MetaData> *metaData) { + static const unsigned channelCountTable[] = {2, 1, 2, 3, 3, 4, 4, 5}; + static const unsigned samplingRateTable[] = {48000, 44100, 32000}; + static const unsigned rates[] = {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, + 320, 384, 448, 512, 576, 640}; + + static const unsigned frameSizeTable[19][3] = { + { 64, 69, 96 }, + { 80, 87, 120 }, + { 96, 104, 144 }, + { 112, 121, 168 }, + { 128, 139, 192 }, + { 160, 174, 240 }, + { 192, 208, 288 }, + { 224, 243, 336 }, + { 256, 278, 384 }, + { 320, 348, 480 }, + { 384, 417, 576 }, + { 448, 487, 672 }, + { 512, 557, 768 }, + { 640, 696, 960 }, + { 768, 835, 1152 }, + { 896, 975, 1344 }, + { 1024, 1114, 1536 }, + { 1152, 1253, 1728 }, + { 1280, 1393, 1920 }, + }; + + ABitReader bits(ptr, size); + unsigned syncStartPos = 0; // in bytes + if (bits.numBitsLeft() < 16) { + return 0; + } + if (bits.getBits(16) != 0x0B77) { + return 0; + } + + if (bits.numBitsLeft() < 16 + 2 + 6 + 5 + 3 + 3) { + ALOGV("Not enough bits left for further parsing"); + return 0; + } + bits.skipBits(16); // crc1 + + unsigned fscod = bits.getBits(2); + if (fscod == 3) { + ALOGW("Incorrect fscod in AC3 header"); + return 0; + } + + unsigned frmsizecod = bits.getBits(6); + if (frmsizecod > 37) { + ALOGW("Incorrect frmsizecod in AC3 header"); + return 0; + } + + unsigned bsid = bits.getBits(5); + if (bsid > 8) { + ALOGW("Incorrect bsid in AC3 header. Possibly E-AC-3?"); + return 0; + } + + unsigned bsmod = bits.getBits(3); + unsigned acmod = bits.getBits(3); + unsigned cmixlev = 0; + unsigned surmixlev = 0; + unsigned dsurmod = 0; + + if ((acmod & 1) > 0 && acmod != 1) { + if (bits.numBitsLeft() < 2) { + return 0; + } + cmixlev = bits.getBits(2); + } + if ((acmod & 4) > 0) { + if (bits.numBitsLeft() < 2) { + return 0; + } + surmixlev = bits.getBits(2); + } + if (acmod == 2) { + if (bits.numBitsLeft() < 2) { + return 0; + } + dsurmod = bits.getBits(2); + } + + if (bits.numBitsLeft() < 1) { + return 0; + } + unsigned lfeon = bits.getBits(1); + + unsigned samplingRate = samplingRateTable[fscod]; + unsigned payloadSize = frameSizeTable[frmsizecod >> 1][fscod]; + if (fscod == 1) { + payloadSize += frmsizecod & 1; + } + payloadSize <<= 1; // convert from 16-bit words to bytes + + unsigned channelCount = channelCountTable[acmod] + lfeon; + + if (metaData != NULL) { + (*metaData)->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AC3); + (*metaData)->setInt32(kKeyChannelCount, channelCount); + (*metaData)->setInt32(kKeySampleRate, samplingRate); + } + + return payloadSize; +} + +static bool IsSeeminglyValidAC3Header(const uint8_t *ptr, size_t size) { + return parseAC3SyncFrame(ptr, size, NULL) > 0; +} + static bool IsSeeminglyValidADTSHeader(const uint8_t *ptr, size_t size) { if (size < 3) { // Not enough data to verify header. @@ -225,6 +341,33 @@ status_t ElementaryStreamQueue::appendData( break; } + case AC3: + { + uint8_t *ptr = (uint8_t *)data; + + ssize_t startOffset = -1; + for (size_t i = 0; i < size; ++i) { + if (IsSeeminglyValidAC3Header(&ptr[i], size - i)) { + startOffset = i; + break; + } + } + + if (startOffset < 0) { + return ERROR_MALFORMED; + } + + if (startOffset > 0) { + ALOGI("found something resembling an AC3 syncword at " + "offset %d", + startOffset); + } + + data = &ptr[startOffset]; + size -= startOffset; + break; + } + case MPEG_AUDIO: { uint8_t *ptr = (uint8_t *)data; @@ -329,6 +472,8 @@ sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnit() { return dequeueAccessUnitH264(); case AAC: return dequeueAccessUnitAAC(); + case AC3: + return dequeueAccessUnitAC3(); case MPEG_VIDEO: return dequeueAccessUnitMPEGVideo(); case MPEG4_VIDEO: @@ -341,6 +486,51 @@ sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnit() { } } +sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAC3() { + unsigned syncStartPos = 0; // in bytes + unsigned payloadSize = 0; + sp<MetaData> format = new MetaData; + while (true) { + if (syncStartPos + 2 >= mBuffer->size()) { + return NULL; + } + + payloadSize = parseAC3SyncFrame( + mBuffer->data() + syncStartPos, + mBuffer->size() - syncStartPos, + &format); + if (payloadSize > 0) { + break; + } + ++syncStartPos; + } + + if (mBuffer->size() < syncStartPos + payloadSize) { + ALOGV("Not enough buffer size for AC3"); + return NULL; + } + + if (mFormat == NULL) { + mFormat = format; + } + + sp<ABuffer> accessUnit = new ABuffer(syncStartPos + payloadSize); + memcpy(accessUnit->data(), mBuffer->data(), syncStartPos + payloadSize); + + int64_t timeUs = fetchTimestamp(syncStartPos + payloadSize); + CHECK_GE(timeUs, 0ll); + accessUnit->meta()->setInt64("timeUs", timeUs); + + memmove( + mBuffer->data(), + mBuffer->data() + syncStartPos + payloadSize, + mBuffer->size() - syncStartPos - payloadSize); + + mBuffer->setRange(0, mBuffer->size() - syncStartPos - payloadSize); + + return accessUnit; +} + sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitPCMAudio() { if (mBuffer->size() < 4) { return NULL; diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h index 66a8087..a2cca77 100644 --- a/media/libstagefright/mpeg2ts/ESQueue.h +++ b/media/libstagefright/mpeg2ts/ESQueue.h @@ -32,6 +32,7 @@ struct ElementaryStreamQueue { enum Mode { H264, AAC, + AC3, MPEG_AUDIO, MPEG_VIDEO, MPEG4_VIDEO, @@ -67,6 +68,7 @@ private: sp<ABuffer> dequeueAccessUnitH264(); sp<ABuffer> dequeueAccessUnitAAC(); + sp<ABuffer> dequeueAccessUnitAC3(); sp<ABuffer> dequeueAccessUnitMPEGAudio(); sp<ABuffer> dequeueAccessUnitMPEGVideo(); sp<ABuffer> dequeueAccessUnitMPEG4Video(); |