Provisional support for secure decryption of media streams.

Change-Id: Ib3982a9c960bfdb0cb7e1b174440b141b194cfbe
This commit is contained in:
Andreas Huber
2012-03-26 11:13:27 -07:00
parent 7061b5ffb3
commit 9b8e496f4d
32 changed files with 940 additions and 39 deletions

View File

@@ -28,6 +28,7 @@
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecList.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/NuMediaExtractor.h>
#include <gui/SurfaceComposerClient.h>
@@ -36,7 +37,9 @@ static void usage(const char *me) {
fprintf(stderr, "usage: %s [-a] use audio\n"
"\t\t[-v] use video\n"
"\t\t[-p] playback\n"
"\t\t[-S] allocate buffers from a surface\n", me);
"\t\t[-S] allocate buffers from a surface\n"
"\t\t[-D] decrypt input buffers\n",
me);
exit(1);
}
@@ -63,7 +66,8 @@ static int decode(
const char *path,
bool useAudio,
bool useVideo,
const android::sp<android::Surface> &surface) {
const android::sp<android::Surface> &surface,
bool decryptInputBuffers) {
using namespace android;
static int64_t kTimeout = 500ll;
@@ -109,13 +113,31 @@ static int decode(
state->mNumBuffersDecoded = 0;
state->mIsAudio = isAudio;
state->mCodec = MediaCodec::CreateByType(
looper, mime.c_str(), false /* encoder */);
if (decryptInputBuffers && !isAudio) {
static const MediaCodecList *list = MediaCodecList::getInstance();
ssize_t index =
list->findCodecByType(mime.c_str(), false /* encoder */);
CHECK_GE(index, 0);
const char *componentName = list->getCodecName(index);
AString fullName = componentName;
fullName.append(".secure");
state->mCodec = MediaCodec::CreateByComponentName(
looper, fullName.c_str());
} else {
state->mCodec = MediaCodec::CreateByType(
looper, mime.c_str(), false /* encoder */);
}
CHECK(state->mCodec != NULL);
err = state->mCodec->configure(
format, isVideo ? surface : NULL, 0 /* flags */);
format, isVideo ? surface : NULL,
decryptInputBuffers ? MediaCodec::CONFIGURE_FLAG_SECURE : 0);
CHECK_EQ(err, (status_t)OK);
@@ -202,12 +224,24 @@ static int decode(
err = extractor->getSampleTime(&timeUs);
CHECK_EQ(err, (status_t)OK);
uint32_t bufferFlags = 0;
uint32_t sampleFlags;
err = extractor->getSampleFlags(&sampleFlags);
CHECK_EQ(err, (status_t)OK);
if (sampleFlags & NuMediaExtractor::SAMPLE_FLAG_ENCRYPTED) {
CHECK(decryptInputBuffers);
bufferFlags |= MediaCodec::BUFFER_FLAG_ENCRYPTED;
}
err = state->mCodec->queueInputBuffer(
index,
0 /* offset */,
buffer->size(),
timeUs,
0 /* flags */);
bufferFlags);
CHECK_EQ(err, (status_t)OK);
@@ -341,9 +375,10 @@ int main(int argc, char **argv) {
bool useVideo = false;
bool playback = false;
bool useSurface = false;
bool decryptInputBuffers = false;
int res;
while ((res = getopt(argc, argv, "havpS")) >= 0) {
while ((res = getopt(argc, argv, "havpSD")) >= 0) {
switch (res) {
case 'a':
{
@@ -369,6 +404,12 @@ int main(int argc, char **argv) {
break;
}
case 'D':
{
decryptInputBuffers = true;
break;
}
case '?':
case 'h':
default:
@@ -440,7 +481,8 @@ int main(int argc, char **argv) {
player->stop();
player->reset();
} else {
decode(looper, argv[0], useAudio, useVideo, surface);
decode(looper, argv[0],
useAudio, useVideo, surface, decryptInputBuffers);
}
if (playback || (useSurface && useVideo)) {

View File

@@ -287,6 +287,11 @@ private:
msg->setInt32("channel-count", numChannels);
msg->setInt32("sample-rate", sampleRate);
int32_t isADTS;
if (meta->findInt32(kKeyIsADTS, &isADTS) && isADTS != 0) {
msg->setInt32("is-adts", true);
}
}
uint32_t type;

63
include/media/ICrypto.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <binder/IInterface.h>
#include <media/stagefright/foundation/ABase.h>
#ifndef ANDROID_ICRYPTO_H_
#define ANDROID_ICRYPTO_H_
namespace android {
struct ICrypto : public IInterface {
DECLARE_META_INTERFACE(Crypto);
virtual status_t initialize() = 0;
virtual status_t terminate() = 0;
virtual status_t setEntitlementKey(
const void *key, size_t keyLength) = 0;
virtual status_t setEntitlementControlMessage(
const void *msg, size_t msgLength) = 0;
// "dstData" is in media_server's address space (but inaccessible).
virtual ssize_t decryptVideo(
const void *iv, size_t ivLength,
const void *srcData, size_t srcDataSize,
void *dstData, size_t dstDataOffset) = 0;
// "dstData" is in the calling process' address space.
virtual ssize_t decryptAudio(
const void *iv, size_t ivLength,
const void *srcData, size_t srcDataSize,
void *dstData, size_t dstDataSize) = 0;
private:
DISALLOW_EVIL_CONSTRUCTORS(ICrypto);
};
struct BnCrypto : public BnInterface<ICrypto> {
virtual status_t onTransact(
uint32_t code, const Parcel &data, Parcel *reply,
uint32_t flags = 0);
};
} // namespace android
#endif // ANDROID_ICRYPTO_H_

View File

@@ -31,6 +31,7 @@
namespace android {
struct ICrypto;
class IMediaRecorder;
class IOMX;
struct IStreamSource;
@@ -47,6 +48,7 @@ public:
virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat) = 0;
virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat) = 0;
virtual sp<IOMX> getOMX() = 0;
virtual sp<ICrypto> makeCrypto() = 0;
// codecs and audio devices usage tracking for the battery app
enum BatteryDataBits {

View File

@@ -89,6 +89,10 @@ private:
kPortIndexOutput = 1
};
enum {
kFlagIsSecure = 1,
};
struct BufferInfo {
enum Status {
OWNED_BY_US,
@@ -118,6 +122,7 @@ private:
sp<FlushingState> mFlushingState;
AString mComponentName;
uint32_t mFlags;
uint32_t mQuirks;
sp<IOMX> mOMX;
IOMX::node_id mNode;
@@ -176,7 +181,8 @@ private:
status_t setupAACCodec(
bool encoder,
int32_t numChannels, int32_t sampleRate, int32_t bitRate);
int32_t numChannels, int32_t sampleRate, int32_t bitRate,
bool isADTS);
status_t selectAudioPortFormat(
OMX_U32 portIndex, OMX_AUDIO_CODINGTYPE desiredFormat);

View File

@@ -27,18 +27,21 @@ namespace android {
struct ABuffer;
struct ACodec;
struct AMessage;
struct ICrypto;
struct SoftwareRenderer;
struct SurfaceTextureClient;
struct MediaCodec : public AHandler {
enum ConfigureFlags {
CONFIGURE_FLAG_ENCODE = 1,
CONFIGURE_FLAG_SECURE = 2,
};
enum BufferFlags {
BUFFER_FLAG_SYNCFRAME = 1,
BUFFER_FLAG_CODECCONFIG = 2,
BUFFER_FLAG_EOS = 4,
BUFFER_FLAG_ENCRYPTED = 8,
};
static sp<MediaCodec> CreateByType(
@@ -137,11 +140,13 @@ private:
kFlagStickyError = 8,
kFlagDequeueInputPending = 16,
kFlagDequeueOutputPending = 32,
kFlagIsSecure = 64,
};
struct BufferInfo {
void *mBufferID;
sp<ABuffer> mData;
sp<ABuffer> mEncryptedData;
sp<AMessage> mNotify;
bool mOwnedByClient;
};
@@ -165,6 +170,8 @@ private:
int32_t mDequeueOutputTimeoutGeneration;
uint32_t mDequeueOutputReplyID;
sp<ICrypto> mCrypto;
MediaCodec(const sp<ALooper> &looper);
static status_t PostAndAwaitResponse(

View File

@@ -128,6 +128,12 @@ enum {
kKeyTextFormatData = 'text', // raw data
kKeyRequiresSecureBuffers = 'secu', // bool (int32_t)
kKeyScrambling = 'scrm', // int32_t
kKeyEMM = 'emm ', // raw data
kKeyECM = 'ecm ', // raw data
kKeyIsADTS = 'adts', // bool (int32_t)
};
enum {

View File

@@ -31,6 +31,11 @@ struct MediaExtractor;
struct MediaSource;
struct NuMediaExtractor : public RefBase {
enum SampleFlags {
SAMPLE_FLAG_SYNC = 1,
SAMPLE_FLAG_ENCRYPTED = 2,
};
NuMediaExtractor();
status_t setDataSource(const char *path);
@@ -46,6 +51,7 @@ struct NuMediaExtractor : public RefBase {
status_t readSampleData(const sp<ABuffer> &buffer);
status_t getSampleTrackIndex(size_t *trackIndex);
status_t getSampleTime(int64_t *sampleTimeUs);
status_t getSampleFlags(uint32_t *sampleFlags);
protected:
virtual ~NuMediaExtractor();
@@ -61,7 +67,9 @@ private:
status_t mFinalResult;
MediaBuffer *mSample;
int64_t mSampleTimeUs;
uint32_t mFlags; // bitmask of "TrackFlags"
uint32_t mSampleFlags;
uint32_t mTrackFlags; // bitmask of "TrackFlags"
};
sp<MediaExtractor> mImpl;

View File

@@ -238,7 +238,11 @@ private:
void setComponentRole();
void setAMRFormat(bool isWAMR, int32_t bitRate);
status_t setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t bitRate);
status_t setAACFormat(
int32_t numChannels, int32_t sampleRate, int32_t bitRate,
bool isADTS);
void setG711Format(int32_t numChannels);
status_t setVideoPortFormatType(

View File

@@ -73,6 +73,7 @@ struct AString {
int compare(const AString &other) const;
bool startsWith(const char *prefix) const;
bool endsWith(const char *suffix) const;
void tolower();

View File

@@ -48,6 +48,7 @@ public class MediaCodec
public static int FLAG_SYNCFRAME = 1;
public static int FLAG_CODECCONFIG = 2;
public static int FLAG_EOS = 4;
public static int FLAG_ENCRYPTED = 8;
/** Instantiate a codec component by mime type. For decoder components
this is the mime type of media that this decoder should be able to
@@ -82,6 +83,7 @@ public class MediaCodec
public native final void release();
public static int CONFIGURE_FLAG_ENCODE = 1;
public static int CONFIGURE_FLAG_SECURE = 2;
/** Configures a component.
* @param format A map of string/value pairs describing the input format

View File

@@ -65,6 +65,13 @@ public class MediaExtractor
// Returns the current sample's presentation time in microseconds.
public native long getSampleTime();
// Keep these in sync with their equivalents in NuMediaExtractor.h
public static final int SAMPLE_FLAG_SYNC = 1;
public static final int SAMPLE_FLAG_ENCRYPTED = 2;
// Returns the current sample's flags.
public native int getSampleFlags();
private static native final void native_init();
private native final void native_setup(String path);
private native final void native_finalize();

View File

@@ -160,6 +160,10 @@ status_t JMediaExtractor::getSampleTime(int64_t *sampleTimeUs) {
return mImpl->getSampleTime(sampleTimeUs);
}
status_t JMediaExtractor::getSampleFlags(uint32_t *sampleFlags) {
return mImpl->getSampleFlags(sampleFlags);
}
} // namespace android
////////////////////////////////////////////////////////////////////////////////
@@ -343,6 +347,28 @@ static jlong android_media_MediaExtractor_getSampleTime(
return sampleTimeUs;
}
static jint android_media_MediaExtractor_getSampleFlags(
JNIEnv *env, jobject thiz) {
sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
if (extractor == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return -1ll;
}
uint32_t sampleFlags;
status_t err = extractor->getSampleFlags(&sampleFlags);
if (err == ERROR_END_OF_STREAM) {
return -1ll;
} else if (err != OK) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return false;
}
return sampleFlags;
}
static void android_media_MediaExtractor_native_init(JNIEnv *env) {
jclass clazz = env->FindClass("android/media/MediaExtractor");
CHECK(clazz != NULL);
@@ -412,6 +438,9 @@ static JNINativeMethod gMethods[] = {
{ "getSampleTime", "()J",
(void *)android_media_MediaExtractor_getSampleTime },
{ "getSampleFlags", "()I",
(void *)android_media_MediaExtractor_getSampleFlags },
{ "native_init", "()V", (void *)android_media_MediaExtractor_native_init },
{ "native_setup", "(Ljava/lang/String;)V",

View File

@@ -43,6 +43,7 @@ struct JMediaExtractor : public RefBase {
status_t readSampleData(jobject byteBuf, size_t offset, size_t *sampleSize);
status_t getSampleTrackIndex(size_t *trackIndex);
status_t getSampleTime(int64_t *sampleTimeUs);
status_t getSampleFlags(uint32_t *sampleFlags);
protected:
virtual ~JMediaExtractor();

View File

@@ -324,7 +324,7 @@ status_t ConvertKeyValueArraysToMessage(
env->DeleteLocalRef(byteArray); byteArray = NULL;
}
msg->setObject(key.c_str(), buffer);
msg->setBuffer(key.c_str(), buffer);
}
}

View File

@@ -17,6 +17,7 @@ LOCAL_SRC_FILES:= \
IAudioFlingerClient.cpp \
IAudioTrack.cpp \
IAudioRecord.cpp \
ICrypto.cpp \
AudioRecord.cpp \
AudioSystem.cpp \
mediaplayer.cpp \

293
media/libmedia/ICrypto.cpp Normal file
View File

@@ -0,0 +1,293 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "ICrypto"
#include <utils/Log.h>
#include <binder/Parcel.h>
#include <media/ICrypto.h>
#include <media/stagefright/foundation/ADebug.h>
namespace android {
enum {
INITIALIZE = IBinder::FIRST_CALL_TRANSACTION,
TERMINATE,
SET_ENTITLEMENT_KEY,
SET_ECM,
DECRYPT_VIDEO,
DECRYPT_AUDIO,
};
struct BpCrypto : public BpInterface<ICrypto> {
BpCrypto(const sp<IBinder> &impl)
: BpInterface<ICrypto>(impl) {
}
virtual status_t initialize() {
Parcel data, reply;
data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
remote()->transact(INITIALIZE, data, &reply);
return reply.readInt32();
}
virtual status_t terminate() {
Parcel data, reply;
data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
remote()->transact(TERMINATE, data, &reply);
return reply.readInt32();
}
virtual status_t setEntitlementKey(
const void *key, size_t keyLength) {
Parcel data, reply;
data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
data.writeInt32(keyLength);
data.write(key, keyLength);
remote()->transact(SET_ENTITLEMENT_KEY, data, &reply);
return reply.readInt32();
}
virtual status_t setEntitlementControlMessage(
const void *msg, size_t msgLength) {
Parcel data, reply;
data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
data.writeInt32(msgLength);
data.write(msg, msgLength);
remote()->transact(SET_ECM, data, &reply);
return reply.readInt32();
}
virtual ssize_t decryptVideo(
const void *iv, size_t ivLength,
const void *srcData, size_t srcDataSize,
void *dstData, size_t dstDataOffset) {
Parcel data, reply;
data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
if (iv == NULL) {
if (ivLength > 0) {
return -EINVAL;
}
data.writeInt32(-1);
} else {
data.writeInt32(ivLength);
data.write(iv, ivLength);
}
data.writeInt32(srcDataSize);
data.write(srcData, srcDataSize);
data.writeIntPtr((intptr_t)dstData);
data.writeInt32(dstDataOffset);
remote()->transact(DECRYPT_VIDEO, data, &reply);
return reply.readInt32();
}
virtual ssize_t decryptAudio(
const void *iv, size_t ivLength,
const void *srcData, size_t srcDataSize,
void *dstData, size_t dstDataSize) {
Parcel data, reply;
data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
if (iv == NULL) {
if (ivLength > 0) {
return -EINVAL;
}
data.writeInt32(-1);
} else {
data.writeInt32(ivLength);
data.write(iv, ivLength);
}
data.writeInt32(srcDataSize);
data.write(srcData, srcDataSize);
data.writeInt32(dstDataSize);
remote()->transact(DECRYPT_AUDIO, data, &reply);
ssize_t res = reply.readInt32();
if (res <= 0) {
return res;
}
reply.read(dstData, res);
return res;
}
private:
DISALLOW_EVIL_CONSTRUCTORS(BpCrypto);
};
IMPLEMENT_META_INTERFACE(Crypto, "android.hardware.ICrypto");
////////////////////////////////////////////////////////////////////////////////
status_t BnCrypto::onTransact(
uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
switch (code) {
case INITIALIZE:
{
CHECK_INTERFACE(ICrypto, data, reply);
reply->writeInt32(initialize());
return OK;
}
case TERMINATE:
{
CHECK_INTERFACE(ICrypto, data, reply);
reply->writeInt32(terminate());
return OK;
}
case SET_ENTITLEMENT_KEY:
{
CHECK_INTERFACE(ICrypto, data, reply);
size_t keyLength = data.readInt32();
void *key = malloc(keyLength);
data.read(key, keyLength);
reply->writeInt32(setEntitlementKey(key, keyLength));
free(key);
key = NULL;
return OK;
}
case SET_ECM:
{
CHECK_INTERFACE(ICrypto, data, reply);
size_t msgLength = data.readInt32();
void *msg = malloc(msgLength);
data.read(msg, msgLength);
reply->writeInt32(setEntitlementControlMessage(msg, msgLength));
free(msg);
msg = NULL;
return OK;
}
case DECRYPT_VIDEO:
{
CHECK_INTERFACE(ICrypto, data, reply);
void *iv = NULL;
int32_t ivLength = data.readInt32();
if (ivLength >= 0) {
iv = malloc(ivLength);
data.read(iv, ivLength);
}
size_t srcDataSize = data.readInt32();
void *srcData = malloc(srcDataSize);
data.read(srcData, srcDataSize);
void *dstData = (void *)data.readIntPtr();
size_t dstDataOffset = data.readInt32();
reply->writeInt32(
decryptVideo(
iv,
ivLength < 0 ? 0 : ivLength,
srcData,
srcDataSize,
dstData,
dstDataOffset));
free(srcData);
srcData = NULL;
if (iv != NULL) {
free(iv);
iv = NULL;
}
return OK;
}
case DECRYPT_AUDIO:
{
CHECK_INTERFACE(ICrypto, data, reply);
void *iv = NULL;
int32_t ivLength = data.readInt32();
if (ivLength >= 0) {
iv = malloc(ivLength);
data.read(iv, ivLength);
}
size_t srcDataSize = data.readInt32();
void *srcData = malloc(srcDataSize);
data.read(srcData, srcDataSize);
size_t dstDataSize = data.readInt32();
void *dstData = malloc(dstDataSize);
ssize_t res =
decryptAudio(
iv,
ivLength < 0 ? 0 : ivLength,
srcData,
srcDataSize,
dstData,
dstDataSize);
reply->writeInt32(res);
if (res > 0) {
reply->write(dstData, res);
}
free(dstData);
dstData = NULL;
free(srcData);
srcData = NULL;
if (iv != NULL) {
free(iv);
iv = NULL;
}
return OK;
}
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
} // namespace android

View File

@@ -20,6 +20,7 @@
#include <binder/Parcel.h>
#include <binder/IMemory.h>
#include <media/ICrypto.h>
#include <media/IMediaPlayerService.h>
#include <media/IMediaRecorder.h>
#include <media/IOMX.h>
@@ -36,6 +37,7 @@ enum {
CREATE_MEDIA_RECORDER,
CREATE_METADATA_RETRIEVER,
GET_OMX,
MAKE_CRYPTO,
ADD_BATTERY_DATA,
PULL_BATTERY_DATA
};
@@ -111,6 +113,13 @@ public:
return interface_cast<IOMX>(reply.readStrongBinder());
}
virtual sp<ICrypto> makeCrypto() {
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
remote()->transact(MAKE_CRYPTO, data, &reply);
return interface_cast<ICrypto>(reply.readStrongBinder());
}
virtual void addBatteryData(uint32_t params) {
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
@@ -191,6 +200,12 @@ status_t BnMediaPlayerService::onTransact(
reply->writeStrongBinder(omx->asBinder());
return NO_ERROR;
} break;
case MAKE_CRYPTO: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
sp<ICrypto> crypto = makeCrypto();
reply->writeStrongBinder(crypto->asBinder());
return NO_ERROR;
} break;
case ADD_BATTERY_DATA: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
uint32_t params = data.readInt32();

View File

@@ -7,6 +7,7 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
Crypto.cpp \
MediaRecorderClient.cpp \
MediaPlayerService.cpp \
MetadataRetrieverClient.cpp \
@@ -42,7 +43,7 @@ LOCAL_C_INCLUDES := \
$(TOP)/frameworks/base/media/libstagefright/include \
$(TOP)/frameworks/base/media/libstagefright/rtsp \
$(TOP)/frameworks/native/include/media/openmax \
$(TOP)/external/tremolo/Tremolo
$(TOP)/external/tremolo/Tremolo \
LOCAL_MODULE:= libmediaplayerservice

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "Crypto"
#include <utils/Log.h>
#include "Crypto.h"
#include <media/stagefright/MediaErrors.h>
namespace android {
Crypto::Crypto() {
}
Crypto::~Crypto() {
}
status_t Crypto::initialize() {
return ERROR_UNSUPPORTED;
}
status_t Crypto::terminate() {
return ERROR_UNSUPPORTED;
}
status_t Crypto::setEntitlementKey(
const void *key, size_t keyLength) {
return ERROR_UNSUPPORTED;
}
status_t Crypto::setEntitlementControlMessage(
const void *msg, size_t msgLength) {
return ERROR_UNSUPPORTED;
}
ssize_t Crypto::decryptVideo(
const void *iv, size_t ivLength,
const void *srcData, size_t srcDataSize,
void *dstData, size_t dstDataOffset) {
return ERROR_UNSUPPORTED;
}
ssize_t Crypto::decryptAudio(
const void *iv, size_t ivLength,
const void *srcData, size_t srcDataSize,
void *dstData, size_t dstDataSize) {
return ERROR_UNSUPPORTED;
}
} // namespace android

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRYPTO_H_
#define CRYPTO_H_
#include <media/ICrypto.h>
#include <utils/threads.h>
namespace android {
struct Crypto : public BnCrypto {
Crypto();
virtual status_t initialize();
virtual status_t terminate();
virtual status_t setEntitlementKey(
const void *key, size_t keyLength);
virtual status_t setEntitlementControlMessage(
const void *msg, size_t msgLength);
virtual ssize_t decryptVideo(
const void *iv, size_t ivLength,
const void *srcData, size_t srcDataSize,
void *dstData, size_t dstDataOffset);
virtual ssize_t decryptAudio(
const void *iv, size_t ivLength,
const void *srcData, size_t srcDataSize,
void *dstData, size_t dstDataSize);
protected:
virtual ~Crypto();
private:
DISALLOW_EVIL_CONSTRUCTORS(Crypto);
};
} // namespace android
#endif // CRYPTO_H_

View File

@@ -70,6 +70,8 @@
#include <OMX.h>
#include "Crypto.h"
namespace android {
sp<MediaPlayerBase> createAAH_TXPlayer();
sp<MediaPlayerBase> createAAH_RXPlayer();
@@ -292,6 +294,16 @@ sp<IOMX> MediaPlayerService::getOMX() {
return mOMX;
}
sp<ICrypto> MediaPlayerService::makeCrypto() {
Mutex::Autolock autoLock(mLock);
if (mCrypto == NULL) {
mCrypto = new Crypto;
}
return mCrypto;
}
status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const
{
const size_t SIZE = 256;

View File

@@ -240,6 +240,7 @@ public:
virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
virtual sp<IOMX> getOMX();
virtual sp<ICrypto> makeCrypto();
virtual status_t dump(int fd, const Vector<String16>& args);
@@ -419,6 +420,7 @@ private:
SortedVector< wp<MediaRecorderClient> > mMediaRecorderClients;
int32_t mNextConnId;
sp<IOMX> mOMX;
sp<ICrypto> mCrypto;
};
// ----------------------------------------------------------------------------

View File

@@ -125,6 +125,11 @@ sp<AMessage> NuPlayer::Decoder::makeFormat(const sp<MetaData> &meta) {
msg->setInt32("channel-count", numChannels);
msg->setInt32("sample-rate", sampleRate);
int32_t isADTS;
if (meta->findInt32(kKeyIsADTS, &isADTS) && isADTS != 0) {
msg->setInt32("is-adts", true);
}
}
int32_t maxInputSize;

View File

@@ -427,24 +427,34 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) {
sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
CHECK(mem.get() != NULL);
IOMX::buffer_id buffer;
BufferInfo info;
info.mStatus = BufferInfo::OWNED_BY_US;
uint32_t requiresAllocateBufferBit =
(portIndex == kPortIndexInput)
? OMXCodec::kRequiresAllocateBufferOnInputPorts
: OMXCodec::kRequiresAllocateBufferOnOutputPorts;
if (mQuirks & requiresAllocateBufferBit) {
if (portIndex == kPortIndexInput && (mFlags & kFlagIsSecure)) {
mem.clear();
void *ptr;
err = mOMX->allocateBuffer(
mNode, portIndex, def.nBufferSize, &info.mBufferID,
&ptr);
info.mData = new ABuffer(ptr, def.nBufferSize);
} else if (mQuirks & requiresAllocateBufferBit) {
err = mOMX->allocateBufferWithBackup(
mNode, portIndex, mem, &buffer);
mNode, portIndex, mem, &info.mBufferID);
} else {
err = mOMX->useBuffer(mNode, portIndex, mem, &buffer);
err = mOMX->useBuffer(mNode, portIndex, mem, &info.mBufferID);
}
if (mem != NULL) {
info.mData = new ABuffer(mem->pointer(), def.nBufferSize);
}
BufferInfo info;
info.mBufferID = buffer;
info.mStatus = BufferInfo::OWNED_BY_US;
info.mData = new ABuffer(mem->pointer(), def.nBufferSize);
mBuffers[portIndex].push(info);
}
}
@@ -840,7 +850,13 @@ status_t ACodec::configureCodec(
|| !msg->findInt32("sample-rate", &sampleRate)) {
err = INVALID_OPERATION;
} else {
err = setupAACCodec(encoder, numChannels, sampleRate, bitRate);
int32_t isADTS;
if (!msg->findInt32("is-adts", &isADTS)) {
isADTS = 0;
}
err = setupAACCodec(
encoder, numChannels, sampleRate, bitRate, isADTS != 0);
}
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
err = setupAMRCodec(encoder, false /* isWAMR */, bitRate);
@@ -934,7 +950,11 @@ status_t ACodec::selectAudioPortFormat(
status_t ACodec::setupAACCodec(
bool encoder,
int32_t numChannels, int32_t sampleRate, int32_t bitRate) {
int32_t numChannels, int32_t sampleRate, int32_t bitRate, bool isADTS) {
if (encoder && isADTS) {
return -EINVAL;
}
status_t err = setupRawAudioFormat(
encoder ? kPortIndexInput : kPortIndexOutput,
sampleRate,
@@ -1021,7 +1041,11 @@ status_t ACodec::setupAACCodec(
profile.nChannels = numChannels;
profile.nSampleRate = sampleRate;
profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
profile.eAACStreamFormat =
isADTS
? OMX_AUDIO_AACStreamFormatMP4ADTS
: OMX_AUDIO_AACStreamFormatMP4FF;
return mOMX->setParameter(
mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
@@ -2653,6 +2677,12 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
observer->setNotificationMessage(notify);
mCodec->mComponentName = componentName;
mCodec->mFlags = 0;
if (componentName.endsWith(".secure")) {
mCodec->mFlags |= kFlagIsSecure;
}
mCodec->mQuirks = quirks;
mCodec->mOMX = omx;
mCodec->mNode = node;
@@ -2701,6 +2731,7 @@ void ACodec::LoadedState::onShutdown(bool keepComponentAllocated) {
mCodec->mNode = NULL;
mCodec->mOMX.clear();
mCodec->mQuirks = 0;
mCodec->mFlags = 0;
mCodec->mComponentName.clear();
mCodec->changeState(mCodec->mUninitializedState);

View File

@@ -22,10 +22,14 @@
#include "include/SoftwareRenderer.h"
#include <binder/IServiceManager.h>
#include <gui/SurfaceTextureClient.h>
#include <media/ICrypto.h>
#include <media/IMediaPlayerService.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/ACodec.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
@@ -528,6 +532,12 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
info.mOwnedByClient = false;
CHECK(msg->findBuffer(name.c_str(), &info.mData));
if (portIndex == kPortIndexInput
&& (mFlags & kFlagIsSecure)) {
info.mEncryptedData =
new ABuffer(info.mData->capacity());
}
buffers->push_back(info);
}
@@ -742,6 +752,59 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
format->setInt32("encoder", true);
}
if (flags & CONFIGURE_FLAG_SECURE) {
mFlags |= kFlagIsSecure;
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder =
sm->getService(String16("media.player"));
sp<IMediaPlayerService> service =
interface_cast<IMediaPlayerService>(binder);
CHECK(service != NULL);
mCrypto = service->makeCrypto();
status_t err = mCrypto->initialize();
if (err == OK) {
sp<ABuffer> emm;
if (format->findBuffer("emm", &emm)) {
err = mCrypto->setEntitlementKey(
emm->data(), emm->size());
}
}
if (err == OK) {
sp<ABuffer> ecm;
if (format->findBuffer("ecm", &ecm)) {
CHECK_EQ(ecm->size(), 80u);
// bytes 16..47 of the original ecm stream data.
err = mCrypto->setEntitlementControlMessage(
ecm->data() + 16, 32);
}
}
if (err != OK) {
ALOGE("failed to instantiate crypto service.");
mCrypto.clear();
setState(INITIALIZED);
sp<AMessage> response = new AMessage;
response->setInt32("err", UNKNOWN_ERROR);
response->postReply(mReplyID);
break;
}
} else {
mFlags &= ~kFlagIsSecure;
}
mCodec->initiateConfigureComponent(format);
break;
}
@@ -983,7 +1046,10 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
for (size_t i = 0; i < srcBuffers.size(); ++i) {
const BufferInfo &info = srcBuffers.itemAt(i);
dstBuffers->push_back(info.mData);
dstBuffers->push_back(
(portIndex == kPortIndexInput
&& (mFlags & kFlagIsSecure))
? info.mEncryptedData : info.mData);
}
(new AMessage)->postReply(replyID);
@@ -1037,10 +1103,15 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
}
void MediaCodec::setState(State newState) {
if (newState == UNINITIALIZED) {
if (newState == INITIALIZED) {
delete mSoftRenderer;
mSoftRenderer = NULL;
if (mCrypto != NULL) {
mCrypto->terminate();
mCrypto.clear();
}
mNativeWindow.clear();
mOutputFormat.clear();
@@ -1150,6 +1221,43 @@ status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {
info->mData->meta()->setInt32("csd", true);
}
if (mFlags & kFlagIsSecure) {
uint8_t iv[16];
memset(iv, 0, sizeof(iv));
ssize_t outLength;
if (mFlags & kFlagIsSoftwareCodec) {
outLength = mCrypto->decryptAudio(
(flags & BUFFER_FLAG_ENCRYPTED) ? iv : NULL,
(flags & BUFFER_FLAG_ENCRYPTED) ? sizeof(iv) : 0,
info->mEncryptedData->base() + offset,
size,
info->mData->base(),
info->mData->capacity());
} else {
outLength = mCrypto->decryptVideo(
(flags & BUFFER_FLAG_ENCRYPTED) ? iv : NULL,
(flags & BUFFER_FLAG_ENCRYPTED) ? sizeof(iv) : 0,
info->mEncryptedData->base() + offset,
size,
info->mData->base(),
0 /* offset */);
}
if (outLength < 0) {
return outLength;
}
if ((size_t)outLength > info->mEncryptedData->capacity()) {
return -ERANGE;
}
info->mData->setRange(0, outLength);
} else if (flags & BUFFER_FLAG_ENCRYPTED) {
return -EINVAL;
}
reply->setBuffer("buffer", info->mData);
reply->post();

View File

@@ -107,6 +107,11 @@ status_t NuMediaExtractor::getTrackFormat(
msg->setInt32("channel-count", numChannels);
msg->setInt32("sample-rate", sampleRate);
int32_t isADTS;
if (meta->findInt32(kKeyIsADTS, &isADTS)) {
msg->setInt32("is-adts", true);
}
}
int32_t maxInputSize;
@@ -232,6 +237,20 @@ status_t NuMediaExtractor::getTrackFormat(
msg->setBuffer("csd-1", buffer);
}
if (meta->findData(kKeyEMM, &type, &data, &size)) {
sp<ABuffer> emm = new ABuffer(size);
memcpy(emm->data(), data, size);
msg->setBuffer("emm", emm);
}
if (meta->findData(kKeyECM, &type, &data, &size)) {
sp<ABuffer> ecm = new ABuffer(size);
memcpy(ecm->data(), data, size);
msg->setBuffer("ecm", ecm);
}
*format = msg;
return OK;
@@ -267,13 +286,14 @@ status_t NuMediaExtractor::selectTrack(size_t index) {
info->mFinalResult = OK;
info->mSample = NULL;
info->mSampleTimeUs = -1ll;
info->mFlags = 0;
info->mSampleFlags = 0;
info->mTrackFlags = 0;
const char *mime;
CHECK(source->getFormat()->findCString(kKeyMIMEType, &mime));
if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
info->mFlags |= kIsVorbis;
info->mTrackFlags |= kIsVorbis;
}
return OK;
@@ -288,6 +308,7 @@ void NuMediaExtractor::releaseTrackSamples() {
info->mSample = NULL;
info->mSampleTimeUs = -1ll;
info->mSampleFlags = 0;
}
}
}
@@ -306,6 +327,7 @@ ssize_t NuMediaExtractor::fetchTrackSamples(int64_t seekTimeUs) {
info->mSample->release();
info->mSample = NULL;
info->mSampleTimeUs = -1ll;
info->mSampleFlags = 0;
}
} else if (info->mFinalResult != OK) {
continue;
@@ -323,11 +345,25 @@ ssize_t NuMediaExtractor::fetchTrackSamples(int64_t seekTimeUs) {
info->mFinalResult = err;
info->mSampleTimeUs = -1ll;
info->mSampleFlags = 0;
continue;
} else {
CHECK(info->mSample != NULL);
CHECK(info->mSample->meta_data()->findInt64(
kKeyTime, &info->mSampleTimeUs));
info->mSampleFlags = 0;
int32_t val;
if (info->mSample->meta_data()->findInt32(
kKeyIsSyncFrame, &val) && val != 0) {
info->mSampleFlags |= SAMPLE_FLAG_SYNC;
}
if (info->mSample->meta_data()->findInt32(
kKeyScrambling, &val) && val != 0) {
info->mSampleFlags |= SAMPLE_FLAG_ENCRYPTED;
}
}
}
@@ -371,7 +407,7 @@ status_t NuMediaExtractor::readSampleData(const sp<ABuffer> &buffer) {
size_t sampleSize = info->mSample->range_length();
if (info->mFlags & kIsVorbis) {
if (info->mTrackFlags & kIsVorbis) {
// Each sample's data is suffixed by the number of page samples
// or -1 if not available.
sampleSize += sizeof(int32_t);
@@ -387,7 +423,7 @@ status_t NuMediaExtractor::readSampleData(const sp<ABuffer> &buffer) {
memcpy((uint8_t *)buffer->data(), src, info->mSample->range_length());
if (info->mFlags & kIsVorbis) {
if (info->mTrackFlags & kIsVorbis) {
int32_t numPageSamples;
if (!info->mSample->meta_data()->findInt32(
kKeyValidSamples, &numPageSamples)) {
@@ -430,4 +466,17 @@ status_t NuMediaExtractor::getSampleTime(int64_t *sampleTimeUs) {
return OK;
}
status_t NuMediaExtractor::getSampleFlags(uint32_t *sampleFlags) {
ssize_t minIndex = fetchTrackSamples();
if (minIndex < 0) {
return ERROR_END_OF_STREAM;
}
TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
*sampleFlags = info->mSampleFlags;
return OK;
}
} // namespace android

View File

@@ -515,7 +515,12 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
CHECK(meta->findInt32(kKeyChannelCount, &numChannels));
CHECK(meta->findInt32(kKeySampleRate, &sampleRate));
status_t err = setAACFormat(numChannels, sampleRate, bitRate);
int32_t isADTS;
if (!meta->findInt32(kKeyIsADTS, &isADTS)) {
isADTS = false;
}
status_t err = setAACFormat(numChannels, sampleRate, bitRate, isADTS);
if (err != OK) {
CODEC_LOGE("setAACFormat() failed (err = %d)", err);
return err;
@@ -3386,11 +3391,17 @@ void OMXCodec::setAMRFormat(bool isWAMR, int32_t bitRate) {
}
}
status_t OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t bitRate) {
if (numChannels > 2)
status_t OMXCodec::setAACFormat(
int32_t numChannels, int32_t sampleRate, int32_t bitRate, bool isADTS) {
if (numChannels > 2) {
ALOGW("Number of channels: (%d) \n", numChannels);
}
if (mIsEncoder) {
if (isADTS) {
return -EINVAL;
}
//////////////// input port ////////////////////
setRawAudioFormat(kPortIndexInput, sampleRate, numChannels);
@@ -3445,7 +3456,9 @@ status_t OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t
&profile, sizeof(profile));
if (err != OK) {
CODEC_LOGE("setParameter('OMX_IndexParamAudioAac') failed (err = %d)", err);
CODEC_LOGE("setParameter('OMX_IndexParamAudioAac') failed "
"(err = %d)",
err);
return err;
}
} else {
@@ -3459,13 +3472,19 @@ status_t OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t
profile.nChannels = numChannels;
profile.nSampleRate = sampleRate;
profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
profile.eAACStreamFormat =
isADTS
? OMX_AUDIO_AACStreamFormatMP4ADTS
: OMX_AUDIO_AACStreamFormatMP4FF;
err = mOMX->setParameter(
mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
if (err != OK) {
CODEC_LOGE("setParameter('OMX_IndexParamAudioAac') failed (err = %d)", err);
CODEC_LOGE("setParameter('OMX_IndexParamAudioAac') failed "
"(err = %d)",
err);
return err;
}
}

View File

@@ -23,6 +23,7 @@
#include "pvmp4audiodecoder_api.h"
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/hexdump.h>
namespace android {
@@ -42,6 +43,7 @@ SoftAAC::SoftAAC(
OMX_COMPONENTTYPE **component)
: SimpleSoftOMXComponent(name, callbacks, appData, component),
mConfig(new tPVMP4AudioDecoderExternal),
mIsADTS(false),
mDecoderBuf(NULL),
mInputBufferCount(0),
mUpsamplingFactor(2),
@@ -140,7 +142,12 @@ OMX_ERRORTYPE SoftAAC::internalGetParameter(
aacParams->nAACtools = 0;
aacParams->nAACERtools = 0;
aacParams->eAACProfile = OMX_AUDIO_AACObjectMain;
aacParams->eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF;
aacParams->eAACStreamFormat =
mIsADTS
? OMX_AUDIO_AACStreamFormatMP4ADTS
: OMX_AUDIO_AACStreamFormatMP4FF;
aacParams->eChannelMode = OMX_AUDIO_ChannelModeStereo;
if (!isConfigured()) {
@@ -215,6 +222,15 @@ OMX_ERRORTYPE SoftAAC::internalSetParameter(
return OMX_ErrorUndefined;
}
if (aacParams->eAACStreamFormat == OMX_AUDIO_AACStreamFormatMP4FF) {
mIsADTS = false;
} else if (aacParams->eAACStreamFormat
== OMX_AUDIO_AACStreamFormatMP4ADTS) {
mIsADTS = true;
} else {
return OMX_ErrorUndefined;
}
return OMX_ErrorNone;
}
@@ -299,8 +315,35 @@ void SoftAAC::onQueueFilled(OMX_U32 portIndex) {
mNumSamplesOutput = 0;
}
mConfig->pInputBuffer = inHeader->pBuffer + inHeader->nOffset;
mConfig->inputBufferCurrentLength = inHeader->nFilledLen;
if (mIsADTS) {
// skip 30 bits, aac_frame_length follows.
// ssssssss ssssiiip ppffffPc ccohCCll llllllll lll?????
const uint8_t *adtsHeader = inHeader->pBuffer + inHeader->nOffset;
CHECK_GE(inHeader->nFilledLen, 7);
bool protectionAbsent = (adtsHeader[1] & 1);
unsigned aac_frame_length =
((adtsHeader[3] & 3) << 11)
| (adtsHeader[4] << 3)
| (adtsHeader[5] >> 5);
CHECK_GE(inHeader->nFilledLen, aac_frame_length);
size_t headerSize = (protectionAbsent ? 7 : 9);
mConfig->pInputBuffer = (UChar *)adtsHeader + headerSize;
mConfig->inputBufferCurrentLength = aac_frame_length - headerSize;
inHeader->nOffset += headerSize;
inHeader->nFilledLen -= headerSize;
} else {
mConfig->pInputBuffer = inHeader->pBuffer + inHeader->nOffset;
mConfig->inputBufferCurrentLength = inHeader->nFilledLen;
}
mConfig->inputBufferMaxLength = 0;
mConfig->inputBufferUsedLength = 0;
mConfig->remainderBits = 0;

View File

@@ -49,6 +49,7 @@ private:
};
tPVMP4AudioDecoderExternal *mConfig;
bool mIsADTS;
void *mDecoderBuf;
size_t mInputBufferCount;

View File

@@ -310,6 +310,16 @@ bool AString::startsWith(const char *prefix) const {
return !strncmp(mData, prefix, strlen(prefix));
}
bool AString::endsWith(const char *suffix) const {
size_t suffixLen = strlen(suffix);
if (mSize < suffixLen) {
return false;
}
return !strcmp(mData + mSize - suffixLen, suffix);
}
AString StringPrintf(const char *format, ...) {
va_list ap;
va_start(ap, format);

View File

@@ -117,6 +117,12 @@ status_t AnotherPacketSource::read(
mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
int32_t scrambling;
if (buffer->meta()->findInt32("scrambling", &scrambling)
&& scrambling != 0) {
mediaBuffer->meta_data()->setInt32(kKeyScrambling, scrambling);
}
*out = mediaBuffer;
return OK;
}