diff options
24 files changed, 1841 insertions, 65 deletions
diff --git a/cmds/screenrecord/FrameOutput.cpp b/cmds/screenrecord/FrameOutput.cpp index 06b1f70..4da16bc 100644 --- a/cmds/screenrecord/FrameOutput.cpp +++ b/cmds/screenrecord/FrameOutput.cpp @@ -87,7 +87,7 @@ status_t FrameOutput::createInputSurface(int width, int height, return NO_ERROR; } -status_t FrameOutput::copyFrame(FILE* fp, long timeoutUsec) { +status_t FrameOutput::copyFrame(FILE* fp, long timeoutUsec, bool rawFrames) { Mutex::Autolock _l(mMutex); ALOGV("copyFrame %ld\n", timeoutUsec); @@ -152,16 +152,20 @@ status_t FrameOutput::copyFrame(FILE* fp, long timeoutUsec) { (endWhenNsec - pixWhenNsec) / 1000000.0); } - // Fill out the header. - size_t headerLen = sizeof(uint32_t) * 5; size_t rgbDataLen = width * height * kOutBytesPerPixel; - size_t packetLen = headerLen - sizeof(uint32_t) + rgbDataLen; - uint8_t header[headerLen]; - setValueLE(&header[0], packetLen); - setValueLE(&header[4], width); - setValueLE(&header[8], height); - setValueLE(&header[12], width * kOutBytesPerPixel); - setValueLE(&header[16], HAL_PIXEL_FORMAT_RGB_888); + + 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 @@ -169,7 +173,6 @@ status_t FrameOutput::copyFrame(FILE* fp, long timeoutUsec) { if (kShowTiming) { startWhenNsec = systemTime(CLOCK_MONOTONIC); } - fwrite(header, 1, headerLen, fp); fwrite(mPixelBuf, 1, rgbDataLen, fp); fflush(fp); if (kShowTiming) { diff --git a/cmds/screenrecord/FrameOutput.h b/cmds/screenrecord/FrameOutput.h index c1148d0..c49ec3b 100644 --- a/cmds/screenrecord/FrameOutput.h +++ b/cmds/screenrecord/FrameOutput.h @@ -45,7 +45,7 @@ public: // specified number of microseconds. // // Returns ETIMEDOUT if the timeout expired before we found a frame. - status_t copyFrame(FILE* fp, long timeoutUsec); + status_t copyFrame(FILE* fp, long timeoutUsec, bool rawFrames); // Prepare to copy frames. Makes the EGL context used by this object current. void prepareToCopy() { diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp index a17fc51..02ed53a 100644 --- a/cmds/screenrecord/screenrecord.cpp +++ b/cmds/screenrecord/screenrecord.cpp @@ -65,7 +65,7 @@ static const char* kMimeTypeAvc = "video/avc"; static bool gVerbose = false; // chatty on stdout static bool gRotate = false; // rotate 90 degrees static enum { - FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES + 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? @@ -563,7 +563,7 @@ static status_t recordScreen(const char* fileName) { sp<MediaCodec> encoder; sp<FrameOutput> frameOutput; sp<IGraphicBufferProducer> encoderInputSurface; - if (gOutputFormat != FORMAT_FRAMES) { + if (gOutputFormat != FORMAT_FRAMES && gOutputFormat != FORMAT_RAW_FRAMES) { err = prepareEncoder(mainDpyInfo.fps, &encoder, &encoderInputSurface); if (err != NO_ERROR && !gSizeSpecified) { @@ -643,7 +643,8 @@ static status_t recordScreen(const char* fileName) { break; } case FORMAT_H264: - case FORMAT_FRAMES: { + case FORMAT_FRAMES: + case FORMAT_RAW_FRAMES: { rawFp = prepareRawOutput(fileName); if (rawFp == NULL) { if (encoder != NULL) encoder->release(); @@ -656,7 +657,7 @@ static status_t recordScreen(const char* fileName) { abort(); } - if (gOutputFormat == FORMAT_FRAMES) { + 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 @@ -676,7 +677,8 @@ static status_t recordScreen(const char* fileName) { // stop was requested, but this will do for now. (It almost // works because wait() wakes when a signal hits, but we // need to handle the edge cases.) - err = frameOutput->copyFrame(rawFp, 250000); + bool rawFrames = gOutputFormat == FORMAT_RAW_FRAMES; + err = frameOutput->copyFrame(rawFp, 250000, rawFrames); if (err == ETIMEDOUT) { err = NO_ERROR; } else if (err != NO_ERROR) { @@ -950,6 +952,8 @@ int main(int argc, char* const argv[]) { 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; diff --git a/include/ndk/NdkMediaCodec.h b/include/ndk/NdkMediaCodec.h index c35c6b3..28d121c 100644 --- a/include/ndk/NdkMediaCodec.h +++ b/include/ndk/NdkMediaCodec.h @@ -29,6 +29,7 @@ #include <android/native_window.h> +#include "NdkMediaCrypto.h" #include "NdkMediaFormat.h" #ifdef __cplusplus @@ -46,6 +47,7 @@ struct AMediaCodecBufferInfo { uint32_t flags; }; typedef struct AMediaCodecBufferInfo AMediaCodecBufferInfo; +typedef struct AMediaCodecCryptoInfo AMediaCodecCryptoInfo; enum { AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM = 4, @@ -81,8 +83,12 @@ int AMediaCodec_delete(AMediaCodec*); /** * Configure the codec. For decoding you would typically get the format from an extractor. */ -int AMediaCodec_configure(AMediaCodec*, const AMediaFormat* format, - ANativeWindow* surface, uint32_t flags); // TODO: other args +int AMediaCodec_configure( + AMediaCodec*, + const AMediaFormat* format, + ANativeWindow* surface, + AMediaCrypto *crypto, + uint32_t flags); /** * Start the codec. A codec must be configured before it can be started, and must be started @@ -127,6 +133,12 @@ int AMediaCodec_queueInputBuffer(AMediaCodec*, size_t idx, off_t offset, size_t size, uint64_t time, uint32_t flags); /** + * Send the specified buffer to the codec for processing. + */ +int AMediaCodec_queueSecureInputBuffer(AMediaCodec*, + size_t idx, off_t offset, AMediaCodecCryptoInfo*, uint64_t time, uint32_t flags); + +/** * Get the index of the next available buffer of processed data. */ ssize_t AMediaCodec_dequeueOutputBuffer(AMediaCodec*, AMediaCodecBufferInfo *info, int64_t timeoutUs); @@ -138,7 +150,6 @@ AMediaFormat* AMediaCodec_getOutputFormat(AMediaCodec*); int AMediaCodec_releaseOutputBuffer(AMediaCodec*, size_t idx, bool render); - typedef void (*OnCodecEvent)(AMediaCodec *codec, void *userdata); /** @@ -150,6 +161,36 @@ typedef void (*OnCodecEvent)(AMediaCodec *codec, void *userdata); int AMediaCodec_setNotificationCallback(AMediaCodec*, OnCodecEvent callback, void *userdata); +enum { + AMEDIACODECRYPTOINFO_MODE_CLEAR = 0, + AMEDIACODECRYPTOINFO_MODE_AES_CTR = 1 +}; + +/** + * create an AMediaCodecCryptoInfo from scratch. Use this if you need to use custom + * crypto info, rather than one obtained from AMediaExtractor. + */ +AMediaCodecCryptoInfo *AMediaCodecCryptoInfo_new( + int numsubsamples, + uint8_t key[16], + uint8_t iv[16], + uint32_t mode, + size_t *clearbytes, + size_t *encryptedbytes); + +/** + * delete an AMediaCodecCryptoInfo created previously with AMediaCodecCryptoInfo_new, or + * obtained from AMediaExtractor + */ +int AMediaCodecCryptoInfo_delete(AMediaCodecCryptoInfo*); + +size_t AMediaCodecCryptoInfo_getNumSubSamples(AMediaCodecCryptoInfo*); +int AMediaCodecCryptoInfo_getKey(AMediaCodecCryptoInfo*, uint8_t *dst); +int AMediaCodecCryptoInfo_getIV(AMediaCodecCryptoInfo*, uint8_t *dst); +uint32_t AMediaCodecCryptoInfo_getMode(AMediaCodecCryptoInfo*); +int AMediaCodecCryptoInfo_getClearBytes(AMediaCodecCryptoInfo*, size_t *dst); +int AMediaCodecCryptoInfo_getEncryptedBytes(AMediaCodecCryptoInfo*, size_t *dst); + #ifdef __cplusplus } // extern "C" #endif diff --git a/include/ndk/NdkMediaCrypto.h b/include/ndk/NdkMediaCrypto.h new file mode 100644 index 0000000..83eaad2 --- /dev/null +++ b/include/ndk/NdkMediaCrypto.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + + +/* + * This file defines an NDK API. + * Do not remove methods. + * Do not change method signatures. + * Do not change the value of constants. + * Do not change the size of any of the classes defined in here. + * Do not reference types that are not part of the NDK. + * Do not #include files that aren't part of the NDK. + */ + +#ifndef _NDK_MEDIA_CRYPTO_H +#define _NDK_MEDIA_CRYPTO_H + +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct AMediaCrypto; +typedef struct AMediaCrypto AMediaCrypto; + +typedef uint8_t AMediaUUID[16]; + +bool AMediaCrypto_isCryptoSchemeSupported(const AMediaUUID uuid); + +bool AMediaCrypto_requiresSecureDecoderComponent(const char *mime); + +AMediaCrypto* AMediaCrypto_new(const AMediaUUID uuid, const void *initData, size_t initDataSize); + +void AMediaCrypto_delete(AMediaCrypto* crypto); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _NDK_MEDIA_CRYPTO_H diff --git a/include/ndk/NdkMediaDrm.h b/include/ndk/NdkMediaDrm.h new file mode 100644 index 0000000..1322a9d --- /dev/null +++ b/include/ndk/NdkMediaDrm.h @@ -0,0 +1,470 @@ +/* + * 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. + */ + +/* + * This file defines an NDK API. + * Do not remove methods. + * Do not change method signatures. + * Do not change the value of constants. + * Do not change the size of any of the classes defined in here. + * Do not reference types that are not part of the NDK. + * Do not #include files that aren't part of the NDK. + */ + +#ifndef _NDK_MEDIA_DRM_H +#define _NDK_MEDIA_DRM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <stdbool.h> + +struct AMediaDrm; +typedef struct AMediaDrm AMediaDrm; + +typedef struct { + const uint8_t *ptr; + size_t length; +} AMediaDrmByteArray; + +typedef AMediaDrmByteArray AMediaDrmSessionId; +typedef AMediaDrmByteArray AMediaDrmScope; +typedef AMediaDrmByteArray AMediaDrmKeySetId; +typedef AMediaDrmByteArray AMediaDrmSecureStop; + +#define MEDIADRM_ERROR_BASE -2000 + +typedef enum { + MEDIADRM_OK = 0, + MEDIADRM_NOT_PROVISIONED_ERROR = MEDIADRM_ERROR_BASE - 1, + MEDIADRM_RESOURCE_BUSY_ERROR = MEDIADRM_ERROR_BASE - 2, + MEDIADRM_DEVICE_REVOKED_ERROR = MEDIADRM_ERROR_BASE - 3, + MEDIADRM_SHORT_BUFFER = MEDIADRM_ERROR_BASE - 4, + MEDIADRM_INVALID_OBJECT_ERROR = MEDIADRM_ERROR_BASE - 5, + MEDIADRM_INVALID_PARAMETER_ERROR = MEDIADRM_ERROR_BASE - 6, + MEDIADRM_SESSION_NOT_OPENED_ERROR = MEDIADRM_ERROR_BASE - 7, + MEDIADRM_TAMPER_DETECTED_ERROR = MEDIADRM_ERROR_BASE - 8, + MEDIADRM_VERIFY_FAILED = MEDIADRM_ERROR_BASE - 9, + MEDIADRM_NEED_KEY_ERROR = MEDIADRM_ERROR_BASE - 10, + MEDIADRM_LICENSE_EXPIRED_ERROR = MEDIADRM_ERROR_BASE - 11, + MEDIADRM_UNKNOWN_ERROR = MEDIADRM_ERROR_BASE - 12, +} mediadrm_status_t; + +typedef enum AMediaDrmEventType { + /** + * This event type indicates that the app needs to request a certificate from + * the provisioning server. The request message data is obtained using + * AMediaDrm_getProvisionRequest. + */ + EVENT_PROVISION_REQUIRED = 1, + + /** + * This event type indicates that the app needs to request keys from a license + * server. The request message data is obtained using AMediaDrm_getKeyRequest. + */ + EVENT_KEY_REQUIRED = 2, + + /** + * This event type indicates that the licensed usage duration for keys in a session + * has expired. The keys are no longer valid. + */ + EVENT_KEY_EXPIRED = 3, + + /** + * This event may indicate some specific vendor-defined condition, see your + * DRM provider documentation for details + */ + EVENT_VENDOR_DEFINED = 4 +} AMediaDrmEventType; + +typedef void (*AMediaDrmEventListener)(AMediaDrm *, const AMediaDrmSessionId &sessionId, + AMediaDrmEventType eventType, int extra, const uint8_t *data, size_t dataSize); + + +/** + * Query if the given scheme identified by its UUID is supported on this device, and + * whether the drm plugin is able to handle the media container format specified by mimeType. + * + * uuid identifies the universal unique ID of the crypto scheme. uuid must be 16 bytes. + * mimeType is the MIME type of the media container, e.g. "video/mp4". If mimeType + * is not known or required, it can be provided as NULL. + */ +bool AMediaDrm_isCryptoSchemeSupported(const uint8_t *uuid, const char *mimeType); + +/** + * Create a MediaDrm instance from a UUID + * uuid identifies the universal unique ID of the crypto scheme. uuid must be 16 bytes. + */ +AMediaDrm* AMediaDrm_createByUUID(const uint8_t *uuid); + +/** + * Release a MediaDrm object + */ +void AMediaDrm_release(AMediaDrm *); + +/** + * Register a callback to be invoked when an event occurs + * + * listener is the callback that will be invoked on event + */ +void AMediaDrm_setOnEventListener(AMediaDrm *, AMediaDrmEventListener listener); + +/** + * Open a new session with the MediaDrm object. A session ID is returned. + * + * returns MEDIADRM_NOT_PROVISIONED_ERROR if provisioning is needed + * returns MEDIADRM_RESOURCE_BUSY_ERROR if required resources are in use + */ +mediadrm_status_t AMediaDrm_openSession(AMediaDrm *, AMediaDrmSessionId &sessionId); + +/** + * Close a session on the MediaDrm object that was previously opened + * with AMediaDrm_openSession. + */ +mediadrm_status_t AMediaDrm_closeSession(AMediaDrm *, const AMediaDrmSessionId &sessionId); + +typedef enum AMediaDrmKeyType { + /** + * This key request type species that the keys will be for online use, they will + * not be saved to the device for subsequent use when the device is not connected + * to a network. + */ + KEY_TYPE_STREAMING = 1, + + /** + * This key request type specifies that the keys will be for offline use, they + * will be saved to the device for use when the device is not connected to a network. + */ + KEY_TYPE_OFFLINE = 2, + + /** + * This key request type specifies that previously saved offline keys should be released. + */ + KEY_TYPE_RELEASE = 3 +} AMediaDrmKeyType; + +/** + * Data type containing {key, value} pair + */ +typedef struct AMediaDrmKeyValuePair { + const char *mKey; + const char *mValue; +} AMediaDrmKeyValue; + +/** + * A key request/response exchange occurs between the app and a license server + * to obtain or release keys used to decrypt encrypted content. + * AMediaDrm_getKeyRequest is used to obtain an opaque key request byte array that + * is delivered to the license server. The opaque key request byte array is + * returned in KeyRequest.data. The recommended URL to deliver the key request to + * is returned in KeyRequest.defaultUrl. + * + * After the app has received the key request response from the server, + * it should deliver to the response to the DRM engine plugin using the method + * AMediaDrm_provideKeyResponse. + * + * scope may be a sessionId or a keySetId, depending on the specified keyType. + * When the keyType is KEY_TYPE_STREAMING or KEY_TYPE_OFFLINE, scope should be set + * to the sessionId the keys will be provided to. When the keyType is + * KEY_TYPE_RELEASE, scope should be set to the keySetId of the keys being released. + * Releasing keys from a device invalidates them for all sessions. + * + * init container-specific data, its meaning is interpreted based on the mime type + * provided in the mimeType parameter. It could contain, for example, the content + * ID, key ID or other data obtained from the content metadata that is required in + * generating the key request. init may be null when keyType is KEY_TYPE_RELEASE. + * + * initSize is the number of bytes of initData + * + * mimeType identifies the mime type of the content. + * + * keyType specifes the type of the request. The request may be to acquire keys for + * streaming or offline content, or to release previously acquired keys, which are + * identified by a keySetId. + * + * optionalParameters are included in the key request message to allow a client + * application to provide additional message parameters to the server. + * + * numOptionalParameters indicates the number of optional parameters provided + * by the caller + * + * On exit: + * 1. The keyRequest pointer will reference the opaque key request data. It + * will reside in memory owned by the AMediaDrm object, and will remain + * accessible until the next call to AMediaDrm_getKeyRequest or until the + * MediaDrm object is released. + * 2. keyRequestSize will be set to the size of the request + * + * returns MEDIADRM_NOT_PROVISIONED_ERROR if reprovisioning is needed, due to a + * problem with the device certificate. +*/ +mediadrm_status_t AMediaDrm_getKeyRequest(AMediaDrm *, const AMediaDrmScope &scope, + const uint8_t *init, size_t initSize, const char *mimeType, AMediaDrmKeyType keyType, + const AMediaDrmKeyValue *optionalParameters, size_t numOptionalParameters, + const uint8_t *&keyRequest, size_t &keyRequestSize); + +/** + * A key response is received from the license server by the app, then it is + * provided to the DRM engine plugin using provideKeyResponse. When the + * response is for an offline key request, a keySetId is returned that can be + * used to later restore the keys to a new session with AMediaDrm_restoreKeys. + * When the response is for a streaming or release request, a null keySetId is + * returned. + * + * scope may be a sessionId or keySetId depending on the type of the + * response. Scope should be set to the sessionId when the response is for either + * streaming or offline key requests. Scope should be set to the keySetId when + * the response is for a release request. + * + * response points to the opaque response from the server + * responseSize should be set to the size of the response in bytes + */ + +mediadrm_status_t AMediaDrm_provideKeyResponse(AMediaDrm *, const AMediaDrmScope &scope, + const uint8_t *response, size_t responseSize, AMediaDrmKeySetId &keySetId); + +/** + * Restore persisted offline keys into a new session. keySetId identifies the + * keys to load, obtained from a prior call to AMediaDrm_provideKeyResponse. + * + * sessionId is the session ID for the DRM session + * keySetId identifies the saved key set to restore + */ +mediadrm_status_t AMediaDrm_restoreKeys(AMediaDrm *, const AMediaDrmSessionId &sessionId, + const AMediaDrmKeySetId &keySetId); + +/** + * Remove the current keys from a session. + * + * keySetId identifies keys to remove + */ +mediadrm_status_t AMediaDrm_removeKeys(AMediaDrm *, const AMediaDrmSessionId &keySetId); + +/** + * Request an informative description of the key status for the session. The status is + * in the form of {key, value} pairs. Since DRM license policies vary by vendor, + * the specific status field names are determined by each DRM vendor. Refer to your + * DRM provider documentation for definitions of the field names for a particular + * DRM engine plugin. + * + * On entry, numPairs should be set by the caller to the maximum number of pairs + * that can be returned (the size of the array). On exit, numPairs will be set + * to the number of entries written to the array. If the number of {key, value} pairs + * to be returned is greater than *numPairs, MEDIADRM_SHORT_BUFFER will be returned + * and numPairs will be set to the number of pairs available. + */ +mediadrm_status_t AMediaDrm_queryKeyStatus(AMediaDrm *, const AMediaDrmSessionId &sessionId, + AMediaDrmKeyValue *keyValuePairs, size_t &numPairs); + + +/** + * A provision request/response exchange occurs between the app and a provisioning + * server to retrieve a device certificate. If provisionining is required, the + * EVENT_PROVISION_REQUIRED event will be sent to the event handler. + * getProvisionRequest is used to obtain the opaque provision request byte array that + * should be delivered to the provisioning server. + * On exit: + * 1. The provision request data will be referenced by provisionRequest, in + * memory owned by the AMediaDrm object. It will remain accessible until the + * next call to getProvisionRequest. + * 2. provisionRequestSize will be set to the size of the request data. + * 3. serverUrl will reference a NULL terminated string containing the URL + * the provisioning request should be sent to. It will remain accessible until + * the next call to getProvisionRequest. + */ +mediadrm_status_t AMediaDrm_getProvisionRequest(AMediaDrm *, const uint8_t *&provisionRequest, + size_t &provisionRequestSize, const char *&serverUrl); + + +/** + * After a provision response is received by the app, it is provided to the DRM + * engine plugin using this method. + * + * response is the opaque provisioning response byte array to provide to the + * DRM engine plugin. + * responseSize is the length of the provisioning response in bytes. + * + * returns MEDIADRM_DEVICE_REVOKED_ERROR if the response indicates that the + * server rejected the request + */ +mediadrm_status_t AMediaDrm_provideProvisionResponse(AMediaDrm *, + const uint8_t *response, size_t responseSize); + + +/** + * A means of enforcing limits on the number of concurrent streams per subscriber + * across devices is provided via SecureStop. This is achieved by securely + * monitoring the lifetime of sessions. + * + * Information from the server related to the current playback session is written + * to persistent storage on the device when each MediaCrypto object is created. + * + * In the normal case, playback will be completed, the session destroyed and the + * Secure Stops will be queried. The app queries secure stops and forwards the + * secure stop message to the server which verifies the signature and notifies the + * server side database that the session destruction has been confirmed. The persisted + * record on the client is only removed after positive confirmation that the server + * received the message using releaseSecureStops(). + * + * numSecureStops is set by the caller to the maximum number of secure stops to + * return. On exit, *numSecureStops will be set to the number actually returned. + * If *numSecureStops is too small for the number of secure stops available, + * MEDIADRM_SHORT_BUFFER will be returned and *numSecureStops will be set to the + * number required. + */ +mediadrm_status_t AMediaDrm_getSecureStops(AMediaDrm *, + AMediaDrmSecureStop *secureStops, size_t &numSecureStops); + +/** + * Process the SecureStop server response message ssRelease. After authenticating + * the message, remove the SecureStops identified in the response. + * + * ssRelease is the server response indicating which secure stops to release + */ +mediadrm_status_t AMediaDrm_releaseSecureStops(AMediaDrm *, + const AMediaDrmSecureStop &ssRelease); + +/** + * String property name: identifies the maker of the DRM engine plugin + */ +const char *PROPERTY_VENDOR = "vendor"; + +/** + * String property name: identifies the version of the DRM engine plugin + */ +const char *PROPERTY_VERSION = "version"; + +/** + * String property name: describes the DRM engine plugin + */ +const char *PROPERTY_DESCRIPTION = "description"; + +/** + * String property name: a comma-separated list of cipher and mac algorithms + * supported by CryptoSession. The list may be empty if the DRM engine + * plugin does not support CryptoSession operations. + */ +const char *PROPERTY_ALGORITHMS = "algorithms"; + +/** + * Read a DRM engine plugin String property value, given the property name string. + * + * propertyName identifies the property to query + * On return, propertyValue will be set to point to the property value. The + * memory that the value resides in is owned by the NDK MediaDrm API and + * will remain valid until the next call to AMediaDrm_getPropertyString. + */ +mediadrm_status_t AMediaDrm_getPropertyString(AMediaDrm *, const char *propertyName, + const char *&propertyValue); + +/** + * Byte array property name: the device unique identifier is established during + * device provisioning and provides a means of uniquely identifying each device. + */ +const char *PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId"; + +/** + * Read a DRM engine plugin byte array property value, given the property name string. + * On return, *propertyValue will be set to point to the property value. The + * memory that the value resides in is owned by the NDK MediaDrm API and + * will remain valid until the next call to AMediaDrm_getPropertyByteArray. + */ +mediadrm_status_t AMediaDrm_getPropertyByteArray(AMediaDrm *, const char *propertyName, + AMediaDrmByteArray &propertyValue); + +/** + * Set a DRM engine plugin String property value. + */ +mediadrm_status_t AMediaDrm_setPropertyString(AMediaDrm *, const char *propertyName, + const char *value); + +/** + * Set a DRM engine plugin byte array property value. + */ +mediadrm_status_t AMediaDrm_setPropertyByteArray(AMediaDrm *, const char *propertyName, + const uint8_t *value, size_t valueSize); + +/** + * In addition to supporting decryption of DASH Common Encrypted Media, the + * MediaDrm APIs provide the ability to securely deliver session keys from + * an operator's session key server to a client device, based on the factory-installed + * root of trust, and then perform encrypt, decrypt, sign and verify operations + * with the session key on arbitrary user data. + * + * Operators create session key servers that receive session key requests and provide + * encrypted session keys which can be used for general purpose crypto operations. + * + * Generic encrypt/decrypt/sign/verify methods are based on the established session + * keys. These keys are exchanged using the getKeyRequest/provideKeyResponse methods. + * + * Applications of this capability include securing various types of purchased or + * private content, such as applications, books and other media, photos or media + * delivery protocols. + */ + +/* + * Encrypt the data referenced by input of length dataSize using algorithm specified + * by cipherAlgorithm, and write the encrypted result into output. The caller must + * ensure that the output buffer is large enough to accept dataSize bytes. The key + * to use is identified by the 16 byte keyId. The key must have been loaded into + * the session using provideKeyResponse. + */ +mediadrm_status_t AMediaDrm_encrypt(AMediaDrm *, const AMediaDrmSessionId &sessionId, + const char *cipherAlgorithm, uint8_t *keyId, uint8_t *iv, + const uint8_t *input, uint8_t *output, size_t dataSize); + +/* + * Decrypt the data referenced by input of length dataSize using algorithm specified + * by cipherAlgorithm, and write the decrypted result into output. The caller must + * ensure that the output buffer is large enough to accept dataSize bytes. The key + * to use is identified by the 16 byte keyId. The key must have been loaded into + * the session using provideKeyResponse. + */ +mediadrm_status_t AMediaDrm_decrypt(AMediaDrm *, const AMediaDrmSessionId &sessionId, + const char *cipherAlgorithm, uint8_t *keyId, uint8_t *iv, + const uint8_t *input, uint8_t *output, size_t dataSize); + +/* + * Generate a signature using the specified macAlgorithm over the message data + * referenced by message of size messageSize and store the signature in the + * buffer referenced signature of max size *signatureSize. If the buffer is not + * large enough to hold the signature, MEDIADRM_SHORT_BUFFER is returned and + * *signatureSize is set to the buffer size required. The key to use is identified + * by the 16 byte keyId. The key must have been loaded into the session using + * provideKeyResponse. + */ +mediadrm_status_t AMediaDrm_sign(AMediaDrm *, const AMediaDrmSessionId &sessionId, + const char *macAlgorithm, uint8_t *keyId, uint8_t *message, size_t messageSize, + uint8_t *signature, size_t *signatureSize); + +/* + * Perform a signature verification using the specified macAlgorithm over the message + * data referenced by the message parameter of size messageSize. Returns MEDIADRM_OK + * if the signature matches, otherwise MEDAIDRM_VERIFY_FAILED is returned. The key to + * use is identified by the 16 byte keyId. The key must have been loaded into the + * session using provideKeyResponse. + */ +mediadrm_status_t AMediaDrm_verify(AMediaDrm *, const AMediaDrmSessionId &sessionId, + const char *macAlgorithm, uint8_t *keyId, const uint8_t *message, size_t messageSize, + const uint8_t *signature, size_t signatureSize); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //_NDK_MEDIA_DRM_H diff --git a/include/ndk/NdkMediaError.h b/include/ndk/NdkMediaError.h new file mode 100644 index 0000000..b89a10e --- /dev/null +++ b/include/ndk/NdkMediaError.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + + +/* + * This file defines an NDK API. + * Do not remove methods. + * Do not change method signatures. + * Do not change the value of constants. + * Do not change the size of any of the classes defined in here. + * Do not reference types that are not part of the NDK. + * Do not #include files that aren't part of the NDK. + */ + +#ifndef _NDK_MEDIA_ERROR_H +#define _NDK_MEDIA_ERROR_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + AMEDIAERROR_BASE = -10000, + + AMEDIAERROR_GENERIC = AMEDIAERROR_BASE, + AMEDIAERROR_MALFORMED = AMEDIAERROR_BASE - 1, + AMEDIAERROR_UNSUPPORTED = AMEDIAERROR_BASE - 2 +}; + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _NDK_MEDIA_ERROR_H diff --git a/include/ndk/NdkMediaExtractor.h b/include/ndk/NdkMediaExtractor.h index a7c32c4..9e50ec0 100644 --- a/include/ndk/NdkMediaExtractor.h +++ b/include/ndk/NdkMediaExtractor.h @@ -30,7 +30,9 @@ #include <sys/types.h> +#include "NdkMediaCodec.h" #include "NdkMediaFormat.h" +#include "NdkMediaCrypto.h" #ifdef __cplusplus extern "C" { @@ -112,6 +114,33 @@ int64_t AMediaExtractor_getSampletime(AMediaExtractor*); */ bool AMediaExtractor_advance(AMediaExtractor*); + +/** + * mapping of crypto scheme uuid to the scheme specific data for that scheme + */ +typedef struct PsshEntry { + AMediaUUID uuid; + size_t datalen; + void *data; +} PsshEntry; + +/** + * list of crypto schemes and their data + */ +typedef struct PsshInfo { + size_t numentries; + PsshEntry entries[0]; +} PsshInfo; + +/** + * Get the PSSH info if present. + */ +PsshInfo* AMediaExtractor_getPsshInfo(AMediaExtractor*); + + +AMediaCodecCryptoInfo *AMediaExtractor_getSampleCryptoInfo(AMediaExtractor *); + + enum { AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC = 1, AMEDIAEXTRACTOR_SAMPLE_FLAG_ENCRYPTED = 2, diff --git a/media/ndk/Android.mk b/media/ndk/Android.mk index b8dd19e..8f795cd 100644 --- a/media/ndk/Android.mk +++ b/media/ndk/Android.mk @@ -22,9 +22,11 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ NdkMediaCodec.cpp \ + NdkMediaCrypto.cpp \ NdkMediaExtractor.cpp \ NdkMediaFormat.cpp \ NdkMediaMuxer.cpp \ + NdkMediaDrm.cpp \ LOCAL_MODULE:= libmediandk @@ -33,13 +35,17 @@ LOCAL_C_INCLUDES := \ frameworks/base/core/jni \ frameworks/av/include/ndk +LOCAL_CFLAGS += -fvisibility=hidden -D EXPORT='__attribute__ ((visibility ("default")))' + LOCAL_SHARED_LIBRARIES := \ + libbinder \ libmedia \ libstagefright \ libstagefright_foundation \ liblog \ libutils \ libandroid_runtime \ + libbinder \ include $(BUILD_SHARED_LIBRARY) diff --git a/media/ndk/NdkMediaCodec.cpp b/media/ndk/NdkMediaCodec.cpp index 1789f75..a7c06d5 100644 --- a/media/ndk/NdkMediaCodec.cpp +++ b/media/ndk/NdkMediaCodec.cpp @@ -18,13 +18,14 @@ #define LOG_TAG "NdkMediaCodec" #include "NdkMediaCodec.h" +#include "NdkMediaError.h" +#include "NdkMediaCryptoPriv.h" #include "NdkMediaFormatPriv.h" #include <utils/Log.h> #include <utils/StrongPointer.h> #include <gui/Surface.h> -#include <media/ICrypto.h> #include <media/stagefright/foundation/ALooper.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/foundation/ABuffer.h> @@ -42,7 +43,7 @@ static int translate_error(status_t err) { return AMEDIACODEC_INFO_TRY_AGAIN_LATER; } ALOGE("sf error code: %d", err); - return -1000; + return AMEDIAERROR_GENERIC; } enum { @@ -158,19 +159,22 @@ static AMediaCodec * createAMediaCodec(const char *name, bool name_is_type, bool return mData; } - +EXPORT AMediaCodec* AMediaCodec_createCodecByName(const char *name) { return createAMediaCodec(name, false, false); } +EXPORT AMediaCodec* AMediaCodec_createDecoderByType(const char *mime_type) { return createAMediaCodec(mime_type, true, false); } +EXPORT AMediaCodec* AMediaCodec_createEncoderByType(const char *name) { return createAMediaCodec(name, true, true); } +EXPORT int AMediaCodec_delete(AMediaCodec *mData) { if (mData->mCodec != NULL) { mData->mCodec->release(); @@ -186,8 +190,13 @@ int AMediaCodec_delete(AMediaCodec *mData) { return OK; } +EXPORT int AMediaCodec_configure( - AMediaCodec *mData, const AMediaFormat* format, ANativeWindow* window, uint32_t flags) { + AMediaCodec *mData, + const AMediaFormat* format, + ANativeWindow* window, + AMediaCrypto *crypto, + uint32_t flags) { sp<AMessage> nativeFormat; AMediaFormat_getFormat(format, &nativeFormat); ALOGV("configure with format: %s", nativeFormat->debugString(0).c_str()); @@ -196,9 +205,11 @@ int AMediaCodec_configure( surface = (Surface*) window; } - return translate_error(mData->mCodec->configure(nativeFormat, surface, NULL, flags)); + return translate_error(mData->mCodec->configure(nativeFormat, surface, + crypto ? crypto->mCrypto : NULL, flags)); } +EXPORT int AMediaCodec_start(AMediaCodec *mData) { status_t ret = mData->mCodec->start(); if (ret != OK) { @@ -210,6 +221,7 @@ int AMediaCodec_start(AMediaCodec *mData) { return OK; } +EXPORT int AMediaCodec_stop(AMediaCodec *mData) { int ret = translate_error(mData->mCodec->stop()); @@ -221,10 +233,12 @@ int AMediaCodec_stop(AMediaCodec *mData) { return ret; } +EXPORT int AMediaCodec_flush(AMediaCodec *mData) { return translate_error(mData->mCodec->flush()); } +EXPORT ssize_t AMediaCodec_dequeueInputBuffer(AMediaCodec *mData, int64_t timeoutUs) { size_t idx; status_t ret = mData->mCodec->dequeueInputBuffer(&idx, timeoutUs); @@ -235,6 +249,7 @@ ssize_t AMediaCodec_dequeueInputBuffer(AMediaCodec *mData, int64_t timeoutUs) { return translate_error(ret); } +EXPORT uint8_t* AMediaCodec_getInputBuffer(AMediaCodec *mData, size_t idx, size_t *out_size) { android::Vector<android::sp<android::ABuffer> > abufs; if (mData->mCodec->getInputBuffers(&abufs) == 0) { @@ -252,6 +267,7 @@ uint8_t* AMediaCodec_getInputBuffer(AMediaCodec *mData, size_t idx, size_t *out_ return NULL; } +EXPORT uint8_t* AMediaCodec_getOutputBuffer(AMediaCodec *mData, size_t idx, size_t *out_size) { android::Vector<android::sp<android::ABuffer> > abufs; if (mData->mCodec->getOutputBuffers(&abufs) == 0) { @@ -269,6 +285,7 @@ uint8_t* AMediaCodec_getOutputBuffer(AMediaCodec *mData, size_t idx, size_t *out return NULL; } +EXPORT int AMediaCodec_queueInputBuffer(AMediaCodec *mData, size_t idx, off_t offset, size_t size, uint64_t time, uint32_t flags) { @@ -277,6 +294,7 @@ int AMediaCodec_queueInputBuffer(AMediaCodec *mData, return translate_error(ret); } +EXPORT ssize_t AMediaCodec_dequeueOutputBuffer(AMediaCodec *mData, AMediaCodecBufferInfo *info, int64_t timeoutUs) { size_t idx; @@ -306,12 +324,14 @@ ssize_t AMediaCodec_dequeueOutputBuffer(AMediaCodec *mData, return translate_error(ret); } +EXPORT AMediaFormat* AMediaCodec_getOutputFormat(AMediaCodec *mData) { sp<AMessage> format; mData->mCodec->getOutputFormat(&format); return AMediaFormat_fromMsg(&format); } +EXPORT int AMediaCodec_releaseOutputBuffer(AMediaCodec *mData, size_t idx, bool render) { if (render) { return translate_error(mData->mCodec->renderOutputBufferAndRelease(idx)); @@ -320,12 +340,143 @@ int AMediaCodec_releaseOutputBuffer(AMediaCodec *mData, size_t idx, bool render) } } +EXPORT int AMediaCodec_setNotificationCallback(AMediaCodec *mData, OnCodecEvent callback, void *userdata) { mData->mCallback = callback; mData->mCallbackUserData = userdata; return OK; } +typedef struct AMediaCodecCryptoInfo { + int numsubsamples; + uint8_t key[16]; + uint8_t iv[16]; + uint32_t mode; + size_t *clearbytes; + size_t *encryptedbytes; +} AMediaCodecCryptoInfo; + +EXPORT +int AMediaCodec_queueSecureInputBuffer( + AMediaCodec* codec, + size_t idx, + off_t offset, + AMediaCodecCryptoInfo* crypto, + uint64_t time, + uint32_t flags) { + + CryptoPlugin::SubSample *subSamples = new CryptoPlugin::SubSample[crypto->numsubsamples]; + for (int i = 0; i < crypto->numsubsamples; i++) { + subSamples[i].mNumBytesOfClearData = crypto->clearbytes[i]; + subSamples[i].mNumBytesOfEncryptedData = crypto->encryptedbytes[i]; + } + + AString errormsg; + status_t err = codec->mCodec->queueSecureInputBuffer(idx, + offset, + subSamples, + crypto->numsubsamples, + crypto->key, + crypto->iv, + (CryptoPlugin::Mode) crypto->mode, + time, + flags, + &errormsg); + if (err != 0) { + ALOGE("queSecureInputBuffer: %s", errormsg.c_str()); + } + delete [] subSamples; + return translate_error(err); +} + + + +EXPORT +AMediaCodecCryptoInfo *AMediaCodecCryptoInfo_new( + int numsubsamples, + uint8_t key[16], + uint8_t iv[16], + uint32_t mode, + size_t *clearbytes, + size_t *encryptedbytes) { + + // size needed to store all the crypto data + size_t cryptosize = sizeof(AMediaCodecCryptoInfo) + sizeof(size_t) * numsubsamples * 2; + AMediaCodecCryptoInfo *ret = (AMediaCodecCryptoInfo*) malloc(cryptosize); + if (!ret) { + ALOGE("couldn't allocate %d bytes", cryptosize); + return NULL; + } + ret->numsubsamples = numsubsamples; + memcpy(ret->key, key, 16); + memcpy(ret->iv, iv, 16); + ret->mode = mode; + + // clearbytes and encryptedbytes point at the actual data, which follows + ret->clearbytes = (size_t*) (ret + 1); // point immediately after the struct + ret->encryptedbytes = ret->clearbytes + numsubsamples; // point after the clear sizes + + memcpy(ret->clearbytes, clearbytes, numsubsamples * sizeof(size_t)); + memcpy(ret->encryptedbytes, encryptedbytes, numsubsamples * sizeof(size_t)); + + return ret; +} + + +EXPORT +int AMediaCodecCryptoInfo_delete(AMediaCodecCryptoInfo* info) { + free(info); + return OK; +} + +EXPORT +size_t AMediaCodecCryptoInfo_getNumSubSamples(AMediaCodecCryptoInfo* ci) { + return ci->numsubsamples; +} + +EXPORT +int AMediaCodecCryptoInfo_getKey(AMediaCodecCryptoInfo* ci, uint8_t *dst) { + if (!dst || !ci) { + return AMEDIAERROR_UNSUPPORTED; + } + memcpy(dst, ci->key, 16); + return OK; +} + +EXPORT +int AMediaCodecCryptoInfo_getIV(AMediaCodecCryptoInfo* ci, uint8_t *dst) { + if (!dst || !ci) { + return AMEDIAERROR_UNSUPPORTED; + } + memcpy(dst, ci->iv, 16); + return OK; +} + +EXPORT +uint32_t AMediaCodecCryptoInfo_getMode(AMediaCodecCryptoInfo* ci) { + if (!ci) { + return AMEDIAERROR_UNSUPPORTED; + } + return ci->mode; +} + +EXPORT +int AMediaCodecCryptoInfo_getClearBytes(AMediaCodecCryptoInfo* ci, size_t *dst) { + if (!dst || !ci) { + return AMEDIAERROR_UNSUPPORTED; + } + memcpy(dst, ci->clearbytes, sizeof(size_t) * ci->numsubsamples); + return OK; +} + +EXPORT +int AMediaCodecCryptoInfo_getEncryptedBytes(AMediaCodecCryptoInfo* ci, size_t *dst) { + if (!dst || !ci) { + return AMEDIAERROR_UNSUPPORTED; + } + memcpy(dst, ci->encryptedbytes, sizeof(size_t) * ci->numsubsamples); + return OK; +} } // extern "C" diff --git a/media/ndk/NdkMediaCrypto.cpp b/media/ndk/NdkMediaCrypto.cpp new file mode 100644 index 0000000..c686273 --- /dev/null +++ b/media/ndk/NdkMediaCrypto.cpp @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#define LOG_NDEBUG 0 +#define LOG_TAG "NdkMediaCrypto" + + +#include "NdkMediaCrypto.h" +#include "NdkMediaCodec.h" +#include "NdkMediaFormatPriv.h" + + +#include <utils/Log.h> +#include <utils/StrongPointer.h> +#include <binder/IServiceManager.h> +#include <media/ICrypto.h> +#include <media/IMediaPlayerService.h> +#include <android_runtime/AndroidRuntime.h> +#include <android_util_Binder.h> + +#include <jni.h> + +using namespace android; + +static int translate_error(status_t err) { + if (err == OK) { + return OK; + } + ALOGE("sf error code: %d", err); + return -1000; +} + + +static sp<ICrypto> makeCrypto() { + sp<IServiceManager> sm = defaultServiceManager(); + + sp<IBinder> binder = + sm->getService(String16("media.player")); + + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + + if (service == NULL) { + return NULL; + } + + sp<ICrypto> crypto = service->makeCrypto(); + + if (crypto == NULL || (crypto->initCheck() != OK && crypto->initCheck() != NO_INIT)) { + return NULL; + } + + return crypto; +} + +struct AMediaCrypto { + sp<ICrypto> mCrypto; +}; + + +extern "C" { + + +EXPORT +bool AMediaCrypto_isCryptoSchemeSupported(const AMediaUUID uuid) { + sp<ICrypto> crypto = makeCrypto(); + if (crypto == NULL) { + return false; + } + return crypto->isCryptoSchemeSupported(uuid); +} + +EXPORT +bool AMediaCrypto_requiresSecureDecoderComponent(const char *mime) { + sp<ICrypto> crypto = makeCrypto(); + if (crypto == NULL) { + return false; + } + return crypto->requiresSecureDecoderComponent(mime); +} + +EXPORT +AMediaCrypto* AMediaCrypto_new(const AMediaUUID uuid, const void *data, size_t datasize) { + + sp<ICrypto> tmp = makeCrypto(); + if (tmp == NULL) { + return NULL; + } + + if (tmp->createPlugin(uuid, data, datasize) != 0) { + return NULL; + } + + AMediaCrypto *crypto = new AMediaCrypto(); + crypto->mCrypto = tmp; + + return crypto; +} + +EXPORT +void AMediaCrypto_delete(AMediaCrypto* crypto) { + delete crypto; +} + + + +} // extern "C" + diff --git a/media/ndk/NdkMediaCryptoPriv.h b/media/ndk/NdkMediaCryptoPriv.h new file mode 100644 index 0000000..14ea928 --- /dev/null +++ b/media/ndk/NdkMediaCryptoPriv.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + + +/* + * This file defines an NDK API. + * Do not remove methods. + * Do not change method signatures. + * Do not change the value of constants. + * Do not change the size of any of the classes defined in here. + * Do not reference types that are not part of the NDK. + * Do not #include files that aren't part of the NDK. + */ + +#ifndef _NDK_MEDIA_CRYPTO_PRIV_H +#define _NDK_MEDIA_CRYPTO_PRIV_H + +#include <sys/types.h> +#include <utils/StrongPointer.h> +#include <media/ICrypto.h> + +using namespace android; + +struct AMediaCrypto { + sp<ICrypto> mCrypto; +}; + +#endif // _NDK_MEDIA_CRYPTO_PRIV_H diff --git a/media/ndk/NdkMediaDrm.cpp b/media/ndk/NdkMediaDrm.cpp new file mode 100644 index 0000000..5e50418 --- /dev/null +++ b/media/ndk/NdkMediaDrm.cpp @@ -0,0 +1,627 @@ +/* + * 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. + */ + +#define LOG_NDEBUG 0 +#define LOG_TAG "NdkMediaDrm" + +#include "NdkMediaDrm.h" + +#include <utils/Log.h> +#include <utils/StrongPointer.h> +#include <gui/Surface.h> + +#include <media/IDrm.h> +#include <media/IDrmClient.h> +#include <media/stagefright/MediaErrors.h> +#include <binder/IServiceManager.h> +#include <media/IMediaPlayerService.h> +#include <ndk/NdkMediaCrypto.h> + + +using namespace android; + +typedef Vector<uint8_t> idvec_t; + +struct AMediaDrm { + sp<IDrm> mDrm; + sp<IDrmClient> mDrmClient; + AMediaDrmEventListener mListener; + List<idvec_t> mIds; + KeyedVector<String8, String8> mQueryResults; + Vector<uint8_t> mKeyRequest; + Vector<uint8_t> mProvisionRequest; + String8 mProvisionUrl; + String8 mPropertyString; + Vector<uint8_t> mPropertyByteArray; + List<Vector<uint8_t> > mSecureStops; +}; + +extern "C" { + +static mediadrm_status_t translateStatus(status_t status) { + mediadrm_status_t result = MEDIADRM_UNKNOWN_ERROR; + switch (status) { + case OK: + result = MEDIADRM_OK; + break; + case android::ERROR_DRM_NOT_PROVISIONED: + result = MEDIADRM_NOT_PROVISIONED_ERROR; + break; + case android::ERROR_DRM_RESOURCE_BUSY: + result = MEDIADRM_RESOURCE_BUSY_ERROR; + break; + case android::ERROR_DRM_DEVICE_REVOKED: + result = MEDIADRM_DEVICE_REVOKED_ERROR; + break; + case android::ERROR_DRM_CANNOT_HANDLE: + result = MEDIADRM_INVALID_PARAMETER_ERROR; + break; + case android::ERROR_DRM_TAMPER_DETECTED: + result = MEDIADRM_TAMPER_DETECTED_ERROR; + break; + case android::ERROR_DRM_SESSION_NOT_OPENED: + result = MEDIADRM_SESSION_NOT_OPENED_ERROR; + break; + case android::ERROR_DRM_NO_LICENSE: + result = MEDIADRM_NEED_KEY_ERROR; + break; + case android::ERROR_DRM_LICENSE_EXPIRED: + result = MEDIADRM_LICENSE_EXPIRED_ERROR; + break; + default: + result = MEDIADRM_UNKNOWN_ERROR; + break; + } + return result; +} + +static sp<IDrm> CreateDrm() { + sp<IServiceManager> sm = defaultServiceManager(); + + sp<IBinder> binder = + sm->getService(String16("media.player")); + + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + + if (service == NULL) { + return NULL; + } + + sp<IDrm> drm = service->makeDrm(); + + if (drm == NULL || (drm->initCheck() != OK && drm->initCheck() != NO_INIT)) { + return NULL; + } + + return drm; +} + + +static sp<IDrm> CreateDrmFromUUID(const AMediaUUID uuid) { + sp<IDrm> drm = CreateDrm(); + + if (drm == NULL) { + return NULL; + } + + status_t err = drm->createPlugin(uuid); + + if (err != OK) { + return NULL; + } + + return drm; +} + +EXPORT +bool AMediaDrm_isCryptoSchemeSupported(const AMediaUUID uuid, const char *mimeType) { + sp<IDrm> drm = CreateDrm(); + + if (drm == NULL) { + return false; + } + + String8 mimeStr = mimeType ? String8(mimeType) : String8(""); + return drm->isCryptoSchemeSupported(uuid, mimeStr); +} + +EXPORT +AMediaDrm* AMediaDrm_createByUUID(const AMediaUUID uuid) { + AMediaDrm *mObj = new AMediaDrm(); + mObj->mDrm = CreateDrmFromUUID(uuid); + return mObj; +} + +EXPORT +void AMediaDrm_release(AMediaDrm *mObj) { + if (mObj->mDrm != NULL) { + mObj->mDrm->setListener(NULL); + mObj->mDrm->destroyPlugin(); + mObj->mDrm.clear(); + } + delete mObj; +} + +#if 0 +void AMediaDrm_setOnEventListener(AMediaDrm *mObj, AMediaDrmEventListener listener) { + mObj->mListener = listener; +} +#endif + + +static bool findId(AMediaDrm *mObj, const AMediaDrmByteArray &id, List<idvec_t>::iterator &iter) { + iter = mObj->mIds.begin(); + while (iter != mObj->mIds.end()) { + if (iter->array() == id.ptr && iter->size() == id.length) { + return true; + } + } + return false; +} + +EXPORT +mediadrm_status_t AMediaDrm_openSession(AMediaDrm *mObj, AMediaDrmSessionId &sessionId) { + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + Vector<uint8_t> session; + status_t status = mObj->mDrm->openSession(session); + if (status == OK) { + mObj->mIds.push_front(session); + List<idvec_t>::iterator iter = mObj->mIds.begin(); + sessionId.ptr = iter->array(); + sessionId.length = iter->size(); + } + return MEDIADRM_OK; +} + +EXPORT +mediadrm_status_t AMediaDrm_closeSession(AMediaDrm *mObj, const AMediaDrmSessionId &sessionId) { + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + + List<idvec_t>::iterator iter; + if (!findId(mObj, sessionId, iter)) { + return MEDIADRM_SESSION_NOT_OPENED_ERROR; + } + mObj->mDrm->closeSession(*iter); + mObj->mIds.erase(iter); + return MEDIADRM_OK; +} + +EXPORT +mediadrm_status_t AMediaDrm_getKeyRequest(AMediaDrm *mObj, const AMediaDrmScope &scope, + const uint8_t *init, size_t initSize, const char *mimeType, AMediaDrmKeyType keyType, + const AMediaDrmKeyValue *optionalParameters, size_t numOptionalParameters, + const uint8_t *&keyRequest, size_t &keyRequestSize) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + if (!mimeType) { + return MEDIADRM_INVALID_PARAMETER_ERROR; + } + + List<idvec_t>::iterator iter; + if (!findId(mObj, scope, iter)) { + return MEDIADRM_SESSION_NOT_OPENED_ERROR; + } + + Vector<uint8_t> mdInit; + mdInit.appendArray(init, initSize); + DrmPlugin::KeyType mdKeyType; + switch (keyType) { + case KEY_TYPE_STREAMING: + mdKeyType = DrmPlugin::kKeyType_Streaming; + break; + case KEY_TYPE_OFFLINE: + mdKeyType = DrmPlugin::kKeyType_Offline; + break; + case KEY_TYPE_RELEASE: + mdKeyType = DrmPlugin::kKeyType_Release; + break; + default: + return MEDIADRM_INVALID_PARAMETER_ERROR; + } + KeyedVector<String8, String8> mdOptionalParameters; + for (size_t i = 0; i < numOptionalParameters; i++) { + mdOptionalParameters.add(String8(optionalParameters[i].mKey), + String8(optionalParameters[i].mValue)); + } + String8 defaultUrl; + status_t status = mObj->mDrm->getKeyRequest(*iter, mdInit, String8(mimeType), + mdKeyType, mdOptionalParameters, mObj->mKeyRequest, defaultUrl); + if (status != OK) { + return translateStatus(status); + } else { + keyRequest = mObj->mKeyRequest.array(); + keyRequestSize = mObj->mKeyRequest.size(); + } + return MEDIADRM_OK; +} + +EXPORT +mediadrm_status_t AMediaDrm_provideKeyResponse(AMediaDrm *mObj, const AMediaDrmScope &scope, + const uint8_t *response, size_t responseSize, AMediaDrmKeySetId &keySetId) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + if (!response || !responseSize) { + return MEDIADRM_INVALID_PARAMETER_ERROR; + } + + List<idvec_t>::iterator iter; + if (!findId(mObj, scope, iter)) { + return MEDIADRM_SESSION_NOT_OPENED_ERROR; + } + Vector<uint8_t> mdResponse; + mdResponse.appendArray(response, responseSize); + + Vector<uint8_t> mdKeySetId; + status_t status = mObj->mDrm->provideKeyResponse(*iter, mdResponse, mdKeySetId); + if (status == OK) { + mObj->mIds.push_front(mdKeySetId); + List<idvec_t>::iterator iter = mObj->mIds.begin(); + keySetId.ptr = iter->array(); + keySetId.length = iter->size(); + } else { + keySetId.ptr = NULL; + keySetId.length = 0; + } + return MEDIADRM_OK; +} + +EXPORT +mediadrm_status_t AMediaDrm_restoreKeys(AMediaDrm *mObj, const AMediaDrmSessionId &sessionId, + const AMediaDrmKeySetId &keySetId) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + List<idvec_t>::iterator iter; + if (!findId(mObj, sessionId, iter)) { + return MEDIADRM_SESSION_NOT_OPENED_ERROR; + } + Vector<uint8_t> keySet; + keySet.appendArray(keySetId.ptr, keySetId.length); + return translateStatus(mObj->mDrm->restoreKeys(*iter, keySet)); +} + +EXPORT +mediadrm_status_t AMediaDrm_removeKeys(AMediaDrm *mObj, const AMediaDrmSessionId &keySetId) { + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + List<idvec_t>::iterator iter; + status_t status; + if (!findId(mObj, keySetId, iter)) { + Vector<uint8_t> keySet; + keySet.appendArray(keySetId.ptr, keySetId.length); + status = mObj->mDrm->removeKeys(keySet); + } else { + status = mObj->mDrm->removeKeys(*iter); + mObj->mIds.erase(iter); + } + return translateStatus(status); +} + +EXPORT +mediadrm_status_t AMediaDrm_queryKeyStatus(AMediaDrm *mObj, const AMediaDrmSessionId &sessionId, + AMediaDrmKeyValue *keyValuePairs, size_t &numPairs) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + List<idvec_t>::iterator iter; + if (!findId(mObj, sessionId, iter)) { + return MEDIADRM_SESSION_NOT_OPENED_ERROR; + } + + status_t status = mObj->mDrm->queryKeyStatus(*iter, mObj->mQueryResults); + if (status != OK) { + numPairs = 0; + return translateStatus(status); + } + + if (mObj->mQueryResults.size() > numPairs) { + numPairs = mObj->mQueryResults.size(); + return MEDIADRM_SHORT_BUFFER; + } + + for (size_t i = 0; i < mObj->mQueryResults.size(); i++) { + keyValuePairs[i].mKey = mObj->mQueryResults.keyAt(i).string(); + keyValuePairs[i].mValue = mObj->mQueryResults.keyAt(i).string(); + } + numPairs = mObj->mQueryResults.size(); + return MEDIADRM_OK; +} + +EXPORT +mediadrm_status_t AMediaDrm_getProvisionRequest(AMediaDrm *mObj, const uint8_t *&provisionRequest, + size_t &provisionRequestSize, const char *&serverUrl) { + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + if (!provisionRequestSize || !serverUrl) { + return MEDIADRM_INVALID_PARAMETER_ERROR; + } + + status_t status = mObj->mDrm->getProvisionRequest(String8(""), String8(""), + mObj->mProvisionRequest, mObj->mProvisionUrl); + if (status != OK) { + return translateStatus(status); + } else { + provisionRequest = mObj->mProvisionRequest.array(); + provisionRequestSize = mObj->mProvisionRequest.size(); + serverUrl = mObj->mProvisionUrl.string(); + } + return MEDIADRM_OK; +} + +EXPORT +mediadrm_status_t AMediaDrm_provideProvisionResponse(AMediaDrm *mObj, + const uint8_t *response, size_t responseSize) { + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + if (!response || !responseSize) { + return MEDIADRM_INVALID_PARAMETER_ERROR; + } + + Vector<uint8_t> mdResponse; + mdResponse.appendArray(response, responseSize); + + Vector<uint8_t> unused; + return translateStatus(mObj->mDrm->provideProvisionResponse(mdResponse, unused, unused)); +} + +EXPORT +mediadrm_status_t AMediaDrm_getSecureStops(AMediaDrm *mObj, + AMediaDrmSecureStop *secureStops, size_t &numSecureStops) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + status_t status = mObj->mDrm->getSecureStops(mObj->mSecureStops); + if (status != OK) { + numSecureStops = 0; + return translateStatus(status); + } + if (numSecureStops < mObj->mSecureStops.size()) { + return MEDIADRM_SHORT_BUFFER; + } + List<Vector<uint8_t> >::iterator iter = mObj->mSecureStops.begin(); + size_t i = 0; + while (iter != mObj->mSecureStops.end()) { + secureStops[i].ptr = iter->array(); + secureStops[i].length = iter->size(); + ++iter; + ++i; + } + numSecureStops = mObj->mSecureStops.size(); + return MEDIADRM_OK; +} + +EXPORT +mediadrm_status_t AMediaDrm_releaseSecureStops(AMediaDrm *mObj, + const AMediaDrmSecureStop &ssRelease) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + + Vector<uint8_t> release; + release.appendArray(ssRelease.ptr, ssRelease.length); + return translateStatus(mObj->mDrm->releaseSecureStops(release)); +} + + +EXPORT +mediadrm_status_t AMediaDrm_getPropertyString(AMediaDrm *mObj, const char *propertyName, + const char *&propertyValue) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + + status_t status = mObj->mDrm->getPropertyString(String8(propertyName), + mObj->mPropertyString); + + if (status == OK) { + propertyValue = mObj->mPropertyString.string(); + } else { + propertyValue = NULL; + } + return translateStatus(status); +} + +EXPORT +mediadrm_status_t AMediaDrm_getPropertyByteArray(AMediaDrm *mObj, + const char *propertyName, AMediaDrmByteArray &propertyValue) { + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + + status_t status = mObj->mDrm->getPropertyByteArray(String8(propertyName), + mObj->mPropertyByteArray); + + if (status == OK) { + propertyValue.ptr = mObj->mPropertyByteArray.array(); + propertyValue.length = mObj->mPropertyByteArray.size(); + } else { + propertyValue.ptr = NULL; + propertyValue.length = 0; + } + return translateStatus(status); +} + +EXPORT +mediadrm_status_t AMediaDrm_setPropertyString(AMediaDrm *mObj, + const char *propertyName, const char *value) { + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + + return translateStatus(mObj->mDrm->setPropertyString(String8(propertyName), + String8(value))); +} + +EXPORT +mediadrm_status_t AMediaDrm_setPropertyByteArray(AMediaDrm *mObj, + const char *propertyName, const uint8_t *value, size_t valueSize) { + + Vector<uint8_t> byteArray; + byteArray.appendArray(value, valueSize); + + return translateStatus(mObj->mDrm->getPropertyByteArray(String8(propertyName), + byteArray)); +} + + +static mediadrm_status_t encrypt_decrypt_common(AMediaDrm *mObj, + const AMediaDrmSessionId &sessionId, + const char *cipherAlgorithm, uint8_t *keyId, uint8_t *iv, + const uint8_t *input, uint8_t *output, size_t dataSize, bool encrypt) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + List<idvec_t>::iterator iter; + if (!findId(mObj, sessionId, iter)) { + return MEDIADRM_SESSION_NOT_OPENED_ERROR; + } + + status_t status = mObj->mDrm->setCipherAlgorithm(*iter, String8(cipherAlgorithm)); + if (status != OK) { + return translateStatus(status); + } + + Vector<uint8_t> keyIdVec; + const size_t kKeyIdSize = 16; + keyIdVec.appendArray(keyId, kKeyIdSize); + + Vector<uint8_t> inputVec; + inputVec.appendArray(input, dataSize); + + Vector<uint8_t> ivVec; + const size_t kIvSize = 16; + ivVec.appendArray(iv, kIvSize); + + Vector<uint8_t> outputVec; + if (encrypt) { + status_t status = mObj->mDrm->encrypt(*iter, keyIdVec, inputVec, ivVec, outputVec); + } else { + status_t status = mObj->mDrm->decrypt(*iter, keyIdVec, inputVec, ivVec, outputVec); + } + if (status == OK) { + memcpy(output, outputVec.array(), outputVec.size()); + } + return translateStatus(status); +} + +EXPORT +mediadrm_status_t AMediaDrm_encrypt(AMediaDrm *mObj, const AMediaDrmSessionId &sessionId, + const char *cipherAlgorithm, uint8_t *keyId, uint8_t *iv, + const uint8_t *input, uint8_t *output, size_t dataSize) { + return encrypt_decrypt_common(mObj, sessionId, cipherAlgorithm, keyId, iv, + input, output, dataSize, true); +} + +EXPORT +mediadrm_status_t AMediaDrm_decrypt(AMediaDrm *mObj, const AMediaDrmSessionId &sessionId, + const char *cipherAlgorithm, uint8_t *keyId, uint8_t *iv, + const uint8_t *input, uint8_t *output, size_t dataSize) { + return encrypt_decrypt_common(mObj, sessionId, cipherAlgorithm, keyId, iv, + input, output, dataSize, false); +} + +EXPORT +mediadrm_status_t AMediaDrm_sign(AMediaDrm *mObj, const AMediaDrmSessionId &sessionId, + const char *macAlgorithm, uint8_t *keyId, uint8_t *message, size_t messageSize, + uint8_t *signature, size_t *signatureSize) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + List<idvec_t>::iterator iter; + if (!findId(mObj, sessionId, iter)) { + return MEDIADRM_SESSION_NOT_OPENED_ERROR; + } + + status_t status = mObj->mDrm->setMacAlgorithm(*iter, String8(macAlgorithm)); + if (status != OK) { + return translateStatus(status); + } + + Vector<uint8_t> keyIdVec; + const size_t kKeyIdSize = 16; + keyIdVec.appendArray(keyId, kKeyIdSize); + + Vector<uint8_t> messageVec; + messageVec.appendArray(message, messageSize); + + Vector<uint8_t> signatureVec; + status = mObj->mDrm->sign(*iter, keyIdVec, messageVec, signatureVec); + if (signatureVec.size() > *signatureSize) { + return MEDIADRM_SHORT_BUFFER; + } + if (status == OK) { + memcpy(signature, signatureVec.array(), signatureVec.size()); + } + return translateStatus(status); +} + +EXPORT +mediadrm_status_t AMediaDrm_verify(AMediaDrm *mObj, const AMediaDrmSessionId &sessionId, + const char *macAlgorithm, uint8_t *keyId, const uint8_t *message, size_t messageSize, + const uint8_t *signature, size_t signatureSize) { + + if (!mObj || mObj->mDrm == NULL) { + return MEDIADRM_INVALID_OBJECT_ERROR; + } + List<idvec_t>::iterator iter; + if (!findId(mObj, sessionId, iter)) { + return MEDIADRM_SESSION_NOT_OPENED_ERROR; + } + + status_t status = mObj->mDrm->setMacAlgorithm(*iter, String8(macAlgorithm)); + if (status != OK) { + return translateStatus(status); + } + + Vector<uint8_t> keyIdVec; + const size_t kKeyIdSize = 16; + keyIdVec.appendArray(keyId, kKeyIdSize); + + Vector<uint8_t> messageVec; + messageVec.appendArray(message, messageSize); + + Vector<uint8_t> signatureVec; + signatureVec.appendArray(signature, signatureSize); + + bool match; + status = mObj->mDrm->verify(*iter, keyIdVec, messageVec, signatureVec, match); + if (status == OK) { + return match ? MEDIADRM_OK : MEDIADRM_VERIFY_FAILED; + } + return translateStatus(status); +} + +} // extern "C" + diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp index 681633a..e23adf3 100644 --- a/media/ndk/NdkMediaExtractor.cpp +++ b/media/ndk/NdkMediaExtractor.cpp @@ -18,12 +18,14 @@ #define LOG_TAG "NdkMediaExtractor" +#include "NdkMediaError.h" #include "NdkMediaExtractor.h" #include "NdkMediaFormatPriv.h" #include <utils/Log.h> #include <utils/StrongPointer.h> +#include <media/hardware/CryptoAPI.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/MetaData.h> @@ -41,16 +43,18 @@ static int translate_error(status_t err) { return OK; } ALOGE("sf error code: %d", err); - return -1000; + return AMEDIAERROR_GENERIC; } struct AMediaExtractor { sp<NuMediaExtractor> mImpl; + sp<ABuffer> mPsshBuf; }; extern "C" { +EXPORT AMediaExtractor* AMediaExtractor_new() { ALOGV("ctor"); AMediaExtractor *mData = new AMediaExtractor(); @@ -58,18 +62,21 @@ AMediaExtractor* AMediaExtractor_new() { return mData; } +EXPORT int AMediaExtractor_delete(AMediaExtractor *mData) { ALOGV("dtor"); delete mData; return OK; } +EXPORT int AMediaExtractor_setDataSourceFd(AMediaExtractor *mData, int fd, off64_t offset, off64_t length) { ALOGV("setDataSource(%d, %lld, %lld)", fd, offset, length); mData->mImpl->setDataSource(fd, offset, length); return 0; } +EXPORT int AMediaExtractor_setDataSource(AMediaExtractor *mData, const char *location) { ALOGV("setDataSource(%s)", location); // TODO: add header support @@ -79,14 +86,14 @@ int AMediaExtractor_setDataSource(AMediaExtractor *mData, const char *location) if (env == NULL) { ALOGE("setDataSource(path) must be called from Java thread"); env->ExceptionClear(); - return -1; + return AMEDIAERROR_UNSUPPORTED; } jclass mediahttpclass = env->FindClass("android/media/MediaHTTPService"); if (mediahttpclass == NULL) { ALOGE("can't find MediaHttpService"); env->ExceptionClear(); - return -1; + return AMEDIAERROR_UNSUPPORTED; } jmethodID mediaHttpCreateMethod = env->GetStaticMethodID(mediahttpclass, @@ -94,7 +101,7 @@ int AMediaExtractor_setDataSource(AMediaExtractor *mData, const char *location) if (mediaHttpCreateMethod == NULL) { ALOGE("can't find method"); env->ExceptionClear(); - return -1; + return AMEDIAERROR_UNSUPPORTED; } jstring jloc = env->NewStringUTF(location); @@ -110,34 +117,40 @@ int AMediaExtractor_setDataSource(AMediaExtractor *mData, const char *location) mData->mImpl->setDataSource(httpService, location, NULL); env->ExceptionClear(); - return 0; + return OK; } +EXPORT int AMediaExtractor_getTrackCount(AMediaExtractor *mData) { return mData->mImpl->countTracks(); } +EXPORT AMediaFormat* AMediaExtractor_getTrackFormat(AMediaExtractor *mData, size_t idx) { sp<AMessage> format; mData->mImpl->getTrackFormat(idx, &format); return AMediaFormat_fromMsg(&format); } +EXPORT int AMediaExtractor_selectTrack(AMediaExtractor *mData, size_t idx) { ALOGV("selectTrack(%z)", idx); return translate_error(mData->mImpl->selectTrack(idx)); } +EXPORT int AMediaExtractor_unselectTrack(AMediaExtractor *mData, size_t idx) { ALOGV("unselectTrack(%z)", idx); return translate_error(mData->mImpl->unselectTrack(idx)); } +EXPORT bool AMediaExtractor_advance(AMediaExtractor *mData) { //ALOGV("advance"); return mData->mImpl->advance(); } +EXPORT int AMediaExtractor_readSampleData(AMediaExtractor *mData, uint8_t *buffer, size_t capacity) { //ALOGV("readSampleData"); sp<ABuffer> tmp = new ABuffer(buffer, capacity); @@ -147,6 +160,7 @@ int AMediaExtractor_readSampleData(AMediaExtractor *mData, uint8_t *buffer, size return -1; } +EXPORT int AMediaExtractor_getSampleFlags(AMediaExtractor *mData) { int sampleFlags = 0; sp<MetaData> meta; @@ -168,6 +182,7 @@ int AMediaExtractor_getSampleFlags(AMediaExtractor *mData) { return sampleFlags; } +EXPORT int AMediaExtractor_getSampleTrackIndex(AMediaExtractor *mData) { size_t idx; if (mData->mImpl->getSampleTrackIndex(&idx) != OK) { @@ -176,6 +191,7 @@ int AMediaExtractor_getSampleTrackIndex(AMediaExtractor *mData) { return idx; } +EXPORT int64_t AMediaExtractor_getSampletime(AMediaExtractor *mData) { int64_t time; if (mData->mImpl->getSampleTime(&time) != OK) { @@ -184,6 +200,143 @@ int64_t AMediaExtractor_getSampletime(AMediaExtractor *mData) { return time; } +EXPORT +PsshInfo* AMediaExtractor_getPsshInfo(AMediaExtractor *ex) { + + if (ex->mPsshBuf != NULL) { + return (PsshInfo*) ex->mPsshBuf->data(); + } + + sp<AMessage> format; + ex->mImpl->getFileFormat(&format); + sp<ABuffer> buffer; + if(!format->findBuffer("pssh", &buffer)) { + return NULL; + } + + // the format of the buffer is 1 or more of: + // { + // 16 byte uuid + // 4 byte data length N + // N bytes of data + // } + + // Determine the number of entries in the source data. + // Since we got the data from stagefright, we trust it is valid and properly formatted. + const uint8_t* data = buffer->data(); + size_t len = buffer->size(); + size_t numentries = 0; + while (len > 0) { + numentries++; + + // skip uuid + data += 16; + len -= 16; + + // get data length + uint32_t datalen = *((uint32_t*)data); + data += 4; + len -= 4; + + // skip the data + data += datalen; + len -= datalen; + } + + // there are <numentries> in the buffer, we need + // (source buffer size) + 4 + (4 * numentries) bytes for the PsshInfo structure + size_t newsize = buffer->size() + 4 + (4 * numentries); + ex->mPsshBuf = new ABuffer(newsize); + ex->mPsshBuf->setRange(0, newsize); + + // copy data + const uint8_t* src = buffer->data(); + uint8_t* dst = ex->mPsshBuf->data(); + uint8_t* dstdata = dst + 4 + numentries * sizeof(PsshEntry); + *((uint32_t*)dst) = numentries; + dst += 4; + for (size_t i = 0; i < numentries; i++) { + // copy uuid + memcpy(dst, src, 16); + src += 16; + dst += 16; + + // get/copy data length + uint32_t datalen = *((uint32_t*)src); + memcpy(dst, src, 4); + src += 4; + dst += 4; + + // the next entry in the destination is a pointer to the actual data, which we store + // after the array of PsshEntry + memcpy(dst, &dstdata, sizeof(dstdata)); + dst += 4; + + // copy the actual data + memcpy(dstdata, src, datalen); + dstdata += datalen; + src += datalen; + } + + return (PsshInfo*) ex->mPsshBuf->data(); +} + +EXPORT +AMediaCodecCryptoInfo *AMediaExtractor_getSampleCryptoInfo(AMediaExtractor *ex) { + sp<MetaData> meta; + if(ex->mImpl->getSampleMeta(&meta) != 0) { + return NULL; + } + + uint32_t type; + const void *crypteddata; + size_t cryptedsize; + if (!meta->findData(kKeyEncryptedSizes, &type, &crypteddata, &cryptedsize)) { + return NULL; + } + size_t numSubSamples = cryptedsize / sizeof(size_t); + + const void *cleardata; + size_t clearsize; + if (meta->findData(kKeyPlainSizes, &type, &cleardata, &clearsize)) { + if (clearsize != cryptedsize) { + // The two must be of the same length. + return NULL; + } + } + + const void *key; + size_t keysize; + if (meta->findData(kKeyCryptoIV, &type, &key, &keysize)) { + if (keysize != 16) { + // IVs must be 16 bytes in length. + return NULL; + } + } + + const void *iv; + size_t ivsize; + if (meta->findData(kKeyCryptoIV, &type, &iv, &ivsize)) { + if (ivsize != 16) { + // IVs must be 16 bytes in length. + return NULL; + } + } + + int32_t mode; + if (!meta->findInt32(kKeyCryptoMode, &mode)) { + mode = CryptoPlugin::kMode_AES_CTR; + } + + return AMediaCodecCryptoInfo_new( + numSubSamples, + (uint8_t*) key, + (uint8_t*) iv, + mode, + (size_t*) cleardata, + (size_t*) crypteddata); +} + } // extern "C" diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp index c08814f..e1d8c95 100644 --- a/media/ndk/NdkMediaFormat.cpp +++ b/media/ndk/NdkMediaFormat.cpp @@ -56,12 +56,14 @@ void AMediaFormat_getFormat(const AMediaFormat* mData, void* dest) { /* * public function follow */ +EXPORT AMediaFormat *AMediaFormat_new() { ALOGV("ctor"); sp<AMessage> msg = new AMessage(); return AMediaFormat_fromMsg(&msg); } +EXPORT int AMediaFormat_delete(AMediaFormat *mData) { ALOGV("dtor"); delete mData; @@ -69,6 +71,7 @@ int AMediaFormat_delete(AMediaFormat *mData) { } +EXPORT const char* AMediaFormat_toString(AMediaFormat *mData) { sp<AMessage> f = mData->mFormat; String8 ret; @@ -141,22 +144,27 @@ const char* AMediaFormat_toString(AMediaFormat *mData) { return mData->mDebug.string(); } +EXPORT bool AMediaFormat_getInt32(AMediaFormat* format, const char *name, int32_t *out) { return format->mFormat->findInt32(name, out); } +EXPORT bool AMediaFormat_getInt64(AMediaFormat* format, const char *name, int64_t *out) { return format->mFormat->findInt64(name, out); } +EXPORT bool AMediaFormat_getFloat(AMediaFormat* format, const char *name, float *out) { return format->mFormat->findFloat(name, out); } +EXPORT bool AMediaFormat_getSize(AMediaFormat* format, const char *name, size_t *out) { return format->mFormat->findSize(name, out); } +EXPORT bool AMediaFormat_getBuffer(AMediaFormat* format, const char *name, void** data, size_t *outsize) { sp<ABuffer> buf; if (format->mFormat->findBuffer(name, &buf)) { @@ -167,6 +175,7 @@ bool AMediaFormat_getBuffer(AMediaFormat* format, const char *name, void** data, return false; } +EXPORT bool AMediaFormat_getString(AMediaFormat* mData, const char *name, const char **out) { for (size_t i = 0; i < mData->mStringCache.size(); i++) { @@ -186,23 +195,28 @@ bool AMediaFormat_getString(AMediaFormat* mData, const char *name, const char ** return false; } +EXPORT void AMediaFormat_setInt32(AMediaFormat* format, const char *name, int32_t value) { format->mFormat->setInt32(name, value); } +EXPORT void AMediaFormat_setInt64(AMediaFormat* format, const char *name, int64_t value) { format->mFormat->setInt64(name, value); } +EXPORT void AMediaFormat_setFloat(AMediaFormat* format, const char* name, float value) { format->mFormat->setFloat(name, value); } +EXPORT void AMediaFormat_setString(AMediaFormat* format, const char* name, const char* value) { // AMessage::setString() makes a copy of the string format->mFormat->setString(name, value, strlen(value)); } +EXPORT void AMediaFormat_setBuffer(AMediaFormat* format, const char* name, void* data, size_t size) { // the ABuffer(void*, size_t) constructor doesn't take ownership of the data, so create // a new buffer and copy the data into it @@ -214,30 +228,30 @@ void AMediaFormat_setBuffer(AMediaFormat* format, const char* name, void* data, } -const char* AMEDIAFORMAT_KEY_AAC_PROFILE = "aac-profile"; -const char* AMEDIAFORMAT_KEY_BIT_RATE = "bitrate"; -const char* AMEDIAFORMAT_KEY_CHANNEL_COUNT = "channel-count"; -const char* AMEDIAFORMAT_KEY_CHANNEL_MASK = "channel-mask"; -const char* AMEDIAFORMAT_KEY_COLOR_FORMAT = "color-format"; -const char* AMEDIAFORMAT_KEY_DURATION = "durationUs"; -const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; -const char* AMEDIAFORMAT_KEY_FRAME_RATE = "frame-rate"; -const char* AMEDIAFORMAT_KEY_HEIGHT = "height"; -const char* AMEDIAFORMAT_KEY_IS_ADTS = "is-adts"; -const char* AMEDIAFORMAT_KEY_IS_AUTOSELECT = "is-autoselect"; -const char* AMEDIAFORMAT_KEY_IS_DEFAULT = "is-default"; -const char* AMEDIAFORMAT_KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; -const char* AMEDIAFORMAT_KEY_I_FRAME_INTERVAL = "i-frame-interval"; -const char* AMEDIAFORMAT_KEY_LANGUAGE = "language"; -const char* AMEDIAFORMAT_KEY_MAX_HEIGHT = "max-height"; -const char* AMEDIAFORMAT_KEY_MAX_INPUT_SIZE = "max-input-size"; -const char* AMEDIAFORMAT_KEY_MAX_WIDTH = "max-width"; -const char* AMEDIAFORMAT_KEY_MIME = "mime"; -const char* AMEDIAFORMAT_KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown"; -const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; -const char* AMEDIAFORMAT_KEY_SAMPLE_RATE = "sample-rate"; -const char* AMEDIAFORMAT_KEY_WIDTH = "width"; -const char* AMEDIAFORMAT_KEY_STRIDE = "stride"; +EXPORT const char* AMEDIAFORMAT_KEY_AAC_PROFILE = "aac-profile"; +EXPORT const char* AMEDIAFORMAT_KEY_BIT_RATE = "bitrate"; +EXPORT const char* AMEDIAFORMAT_KEY_CHANNEL_COUNT = "channel-count"; +EXPORT const char* AMEDIAFORMAT_KEY_CHANNEL_MASK = "channel-mask"; +EXPORT const char* AMEDIAFORMAT_KEY_COLOR_FORMAT = "color-format"; +EXPORT const char* AMEDIAFORMAT_KEY_DURATION = "durationUs"; +EXPORT const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; +EXPORT const char* AMEDIAFORMAT_KEY_FRAME_RATE = "frame-rate"; +EXPORT const char* AMEDIAFORMAT_KEY_HEIGHT = "height"; +EXPORT const char* AMEDIAFORMAT_KEY_IS_ADTS = "is-adts"; +EXPORT const char* AMEDIAFORMAT_KEY_IS_AUTOSELECT = "is-autoselect"; +EXPORT const char* AMEDIAFORMAT_KEY_IS_DEFAULT = "is-default"; +EXPORT const char* AMEDIAFORMAT_KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; +EXPORT const char* AMEDIAFORMAT_KEY_I_FRAME_INTERVAL = "i-frame-interval"; +EXPORT const char* AMEDIAFORMAT_KEY_LANGUAGE = "language"; +EXPORT const char* AMEDIAFORMAT_KEY_MAX_HEIGHT = "max-height"; +EXPORT const char* AMEDIAFORMAT_KEY_MAX_INPUT_SIZE = "max-input-size"; +EXPORT const char* AMEDIAFORMAT_KEY_MAX_WIDTH = "max-width"; +EXPORT const char* AMEDIAFORMAT_KEY_MIME = "mime"; +EXPORT const char* AMEDIAFORMAT_KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown"; +EXPORT const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; +EXPORT const char* AMEDIAFORMAT_KEY_SAMPLE_RATE = "sample-rate"; +EXPORT const char* AMEDIAFORMAT_KEY_WIDTH = "width"; +EXPORT const char* AMEDIAFORMAT_KEY_STRIDE = "stride"; } // extern "C" diff --git a/media/ndk/NdkMediaMuxer.cpp b/media/ndk/NdkMediaMuxer.cpp index 98129cb..aa78740 100644 --- a/media/ndk/NdkMediaMuxer.cpp +++ b/media/ndk/NdkMediaMuxer.cpp @@ -52,6 +52,7 @@ struct AMediaMuxer { extern "C" { +EXPORT AMediaMuxer* AMediaMuxer_new(int fd, OutputFormat format) { ALOGV("ctor"); AMediaMuxer *mData = new AMediaMuxer(); @@ -59,34 +60,41 @@ AMediaMuxer* AMediaMuxer_new(int fd, OutputFormat format) { return mData; } +EXPORT int AMediaMuxer_delete(AMediaMuxer *muxer) { ALOGV("dtor"); delete muxer; return OK; } +EXPORT int AMediaMuxer_setLocation(AMediaMuxer *muxer, float latitude, float longtitude) { return translate_error(muxer->mImpl->setLocation(latitude * 10000, longtitude * 10000)); } +EXPORT int AMediaMuxer_setOrientationHint(AMediaMuxer *muxer, int degrees) { return translate_error(muxer->mImpl->setOrientationHint(degrees)); } +EXPORT ssize_t AMediaMuxer_addTrack(AMediaMuxer *muxer, const AMediaFormat *format) { sp<AMessage> msg; AMediaFormat_getFormat(format, &msg); return translate_error(muxer->mImpl->addTrack(msg)); } +EXPORT int AMediaMuxer_start(AMediaMuxer *muxer) { return translate_error(muxer->mImpl->start()); } +EXPORT int AMediaMuxer_stop(AMediaMuxer *muxer) { return translate_error(muxer->mImpl->stop()); } +EXPORT int AMediaMuxer_writeSampleData(AMediaMuxer *muxer, size_t trackIdx, const uint8_t *data, const AMediaCodecBufferInfo &info) { sp<ABuffer> buf = new ABuffer((void*)(data + info.offset), info.size); diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index c1d4c08..d69d6a2 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -18,6 +18,7 @@ #ifndef ANDROID_AUDIO_FLINGER_H #define ANDROID_AUDIO_FLINGER_H +#include "Configuration.h" #include <stdint.h> #include <sys/types.h> #include <limits.h> diff --git a/services/audioflinger/FastThreadState.cpp b/services/audioflinger/FastThreadState.cpp index d4d6255..6994872 100644 --- a/services/audioflinger/FastThreadState.cpp +++ b/services/audioflinger/FastThreadState.cpp @@ -35,7 +35,7 @@ FastThreadDumpState::FastThreadDumpState() : /* mMeasuredWarmupTs({0, 0}), */ mWarmupCycles(0) #ifdef FAST_MIXER_STATISTICS - , mSamplingN(0), mBounds(0) + , mSamplingN(1), mBounds(0) #endif { mMeasuredWarmupTs.tv_sec = 0; diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h index e9c6834..08b1728 100644 --- a/services/audioflinger/PlaybackTracks.h +++ b/services/audioflinger/PlaybackTracks.h @@ -115,7 +115,6 @@ public: void invalidate(); bool isInvalid() const { return mIsInvalid; } virtual bool isTimedTrack() const { return false; } - bool isFastTrack() const { return (mFlags & IAudioFlinger::TRACK_FAST) != 0; } int fastIndex() const { return mFastIndex; } protected: @@ -142,8 +141,6 @@ protected: // audio HAL when this track will be fully rendered // zero means not monitoring private: - IAudioFlinger::track_flags_t mFlags; - // The following fields are only for fast tracks, and should be in a subclass int mFastIndex; // index within FastMixerState::mFastTracks[]; // either mFastIndex == -1 if not isFastTrack() diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h index 4ca2ad4..fe15571 100644 --- a/services/audioflinger/RecordTracks.h +++ b/services/audioflinger/RecordTracks.h @@ -30,7 +30,7 @@ public: size_t frameCount, int sessionId, int uid, - bool isFast); + IAudioFlinger::track_flags_t flags); virtual ~RecordTrack(); virtual status_t start(AudioSystem::sync_event_t event, int triggerSession); diff --git a/services/audioflinger/StateQueue.h b/services/audioflinger/StateQueue.h index 9cde642..9e176c4 100644 --- a/services/audioflinger/StateQueue.h +++ b/services/audioflinger/StateQueue.h @@ -89,6 +89,8 @@ // arithmetic on the state pointers. However to the mutator, the state pointers // are in a definite circular order. +#include "Configuration.h" + namespace android { #ifdef STATE_QUEUE_DUMP diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp index 8243a8b..2d4e025 100644 --- a/services/audioflinger/Threads.cpp +++ b/services/audioflinger/Threads.cpp @@ -5142,7 +5142,7 @@ sp<AudioFlinger::RecordThread::RecordTrack> AudioFlinger::RecordThread::createRe track = new RecordTrack(this, client, sampleRate, format, channelMask, frameCount, sessionId, uid, - (*flags & IAudioFlinger::TRACK_FAST) != 0); + *flags); lStatus = track->initCheck(); if (lStatus != NO_ERROR) { diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h index 06023fd..5f13be3 100644 --- a/services/audioflinger/TrackBase.h +++ b/services/audioflinger/TrackBase.h @@ -48,6 +48,7 @@ public: const sp<IMemory>& sharedBuffer, int sessionId, int uid, + IAudioFlinger::track_flags_t flags, bool isOut, bool useReadOnlyHeap = false); virtual ~TrackBase(); @@ -63,6 +64,7 @@ public: virtual status_t setSyncEvent(const sp<SyncEvent>& event); sp<IMemory> getBuffers() const { return mBufferMemory; } + bool isFastTrack() const { return (mFlags & IAudioFlinger::TRACK_FAST) != 0; } protected: TrackBase(const TrackBase&); @@ -134,6 +136,7 @@ protected: const int mSessionId; int mUid; Vector < sp<SyncEvent> >mSyncEvents; + const IAudioFlinger::track_flags_t mFlags; const bool mIsOut; ServerProxy* mServerProxy; const int mId; diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp index 88ead74..6dc7f30 100644 --- a/services/audioflinger/Tracks.cpp +++ b/services/audioflinger/Tracks.cpp @@ -69,6 +69,7 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( const sp<IMemory>& sharedBuffer, int sessionId, int clientUid, + IAudioFlinger::track_flags_t flags, bool isOut, bool useReadOnlyHeap) : RefBase(), @@ -85,6 +86,7 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( mChannelCount * audio_bytes_per_sample(format) : sizeof(int8_t)), mFrameCount(frameCount), mSessionId(sessionId), + mFlags(flags), mIsOut(isOut), mServerProxy(NULL), mId(android_atomic_inc(&nextTrackId)), @@ -349,7 +351,7 @@ AudioFlinger::PlaybackThread::Track::Track( int uid, IAudioFlinger::track_flags_t flags) : TrackBase(thread, client, sampleRate, format, channelMask, frameCount, sharedBuffer, - sessionId, uid, true /*isOut*/), + sessionId, uid, flags, true /*isOut*/), mFillingUpStatus(FS_INVALID), // mRetryCount initialized later when needed mSharedBuffer(sharedBuffer), @@ -359,7 +361,6 @@ AudioFlinger::PlaybackThread::Track::Track( mAuxBuffer(NULL), mAuxEffectId(0), mHasVolumeController(false), mPresentationCompleteFrames(0), - mFlags(flags), mFastIndex(-1), mCachedVolume(1.0), mIsInvalid(false), @@ -1833,10 +1834,11 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack( size_t frameCount, int sessionId, int uid, - bool isFast) + IAudioFlinger::track_flags_t flags) : TrackBase(thread, client, sampleRate, format, - channelMask, frameCount, 0 /*sharedBuffer*/, sessionId, uid, false /*isOut*/, - isFast /*useReadOnlyHeap*/), + channelMask, frameCount, 0 /*sharedBuffer*/, sessionId, uid, + flags, false /*isOut*/, + (flags & IAudioFlinger::TRACK_FAST) != 0 /*useReadOnlyHeap*/), mOverflow(false), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpOutFrameCount(0), // See real initialization of mRsmpInFront at RecordThread::start() mRsmpInUnrel(0), mRsmpInFront(0), mFramesToDrop(0), mResamplerBufferProvider(NULL) |