ExoPlayer音视频解密算法移植
ExoPlayer
ExoPlayer的源码都是开放的,在接口中提供了DataSource类作为初始化构建播放音视频文件的方式,直接使用ExoPlayer播放时使用的是默认的DefaultDataSource,也可以使用自定义的数据工厂类来实现自己需要的功能。
AitripDataSource.java
/**
* 自定义的数据工厂类
*/
public class AitripDataSource implements DataSource
{
private static final String TAG = "AitripDataSource";
private static final String SCHEME_ASSET = "asset";
private static final String SCHEME_CONTENT = "content";
private static final String SCHEME_RTMP = "rtmp";
private final Context context;
private final TransferListener listener;
private final DataSource baseDataSource;
// Lazily initialized.
private DataSource fileDataSource;
private DataSource assetDataSource;
private DataSource contentDataSource;
private DataSource rtmpDataSource;
private DataSource dataSource;
/**
* Constructs a new instance that delegates to a provided {@link DataSource} for URI schemes other than file, asset
* and content.
*
* @param context A context.
* @param listener An optional listener.
* @param baseDataSource A {@link DataSource} to use for URI schemes other than file, asset and content. This
* {@link DataSource} should normally support at least http(s).
*/
public AitripDataSource(Context context, TransferListener listener, DataSource baseDataSource)
{
this.context = context.getApplicationContext();
this.listener = listener;
this.baseDataSource = Assertions.checkNotNull(baseDataSource);
}
@Override
public void addTransferListener(TransferListener transferListener)
{
}
// 解密的地方
@Override
public long open(DataSpec dataSpec)
throws IOException
{
Assertions.checkState(dataSource == null);
// Choose the correct source for the scheme.
// 选择正确的数据源方案
String scheme = dataSpec.uri.getScheme();
// 如果URI是一个本地文件路径或本地文件的引用。
if (Util.isLocalFileUri(dataSpec.uri))
{
// if (dataSpec.uri.getPath().endsWith(".mvh5"))
// 如果路径尾包含mvh5的文件名,使用解密类
if (dataSpec.uri.getPath().endsWith(".mvh5") ||
dataSpec.uri.getPath().endsWith(".mp36") ||
dataSpec.uri.getPath().endsWith(".hu6d"))
{
dataSource = new AESFileDataSource();
}
else
{// 否则,正常解析
if (dataSpec.uri.getPath().startsWith("/android_asset/"))
{
dataSource = getAssetDataSource();
}
else
{
dataSource = getFileDataSource();
}
}
}
else if (SCHEME_ASSET.equals(scheme))
{
dataSource = getAssetDataSource();
}
else if (SCHEME_CONTENT.equals(scheme))
{
dataSource = getContentDataSource();
}
else if (SCHEME_RTMP.equals(scheme))
{
dataSource = getRtmpDataSource();
}
else
{
dataSource = baseDataSource;
}
// Open the source and return.
return dataSource.open(dataSpec);
}
@Override
public int read(byte[] buffer, int offset, int readLength)
throws IOException
{
// Log.i(TAG, "AitripDataSource.read() " +offset +" " +readLength);
return dataSource.read(buffer, offset, readLength);
}
@Override
public Uri getUri()
{
return dataSource == null ? null : dataSource.getUri();
}
@Override
public void close()
throws IOException
{
if (dataSource != null)
{
try
{
dataSource.close();
}
finally
{
dataSource = null;
}
}
}
private DataSource getFileDataSource()
{
if (fileDataSource == null)
{
fileDataSource = new FileDataSource();
}
return fileDataSource;
}
private DataSource getAssetDataSource()
{
if (assetDataSource == null)
{
assetDataSource = new AssetDataSource(context);
}
return assetDataSource;
}
private DataSource getContentDataSource()
{
if (contentDataSource == null)
{
contentDataSource = new ContentDataSource(context);
}
return contentDataSource;
}
private DataSource getRtmpDataSource()
{
if (rtmpDataSource == null)
{
try
{
Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.rtmp.RtmpDataSource");
rtmpDataSource = (DataSource)clazz.getDeclaredConstructor().newInstance();
}
catch (ClassNotFoundException e)
{
Log.w(TAG, "Attempting to play RTMP stream without depending on the RTMP extension");
}
catch (InstantiationException e)
{
Log.e(TAG, "Error instantiating RtmpDataSource", e);
}
catch (IllegalAccessException e)
{
Log.e(TAG, "Error instantiating RtmpDataSource", e);
}
catch (NoSuchMethodException e)
{
Log.e(TAG, "Error instantiating RtmpDataSource", e);
}
catch (InvocationTargetException e)
{
Log.e(TAG, "Error instantiating RtmpDataSource", e);
}
if (rtmpDataSource == null)
{
rtmpDataSource = baseDataSource;
}
}
return rtmpDataSource;
}
}
AESFileDataSource.java
public final class AESFileDataSource extends BaseDataSource {
private static final String TAG = "AESFileDataSource";
/**
* Thrown when IOException is encountered during local file read operation.
*/
public static class FileDataSourceException extends IOException {
public FileDataSourceException(IOException cause) {
super(cause);
}
}
private @Nullable
RandomAccessFile file;
private @Nullable Uri uri;
private long bytesRemaining;
private boolean opened;
private byte[] ibuffer = new byte[512000];
// having reached the end of the underlying input stream
private boolean done = false;
// the offset pointing to the next "new" byte
private int ostart = 0;
// the offset pointing to the last "new" byte
private int ofinish = 0;
private long startpos = 0;
private long pos = 0;
private boolean opensignal;
private String suffix;
public WMFileDataSource() {
super(/* isNetwork= */ false);
Log.d(TAG, "super(/* isNetwork= */ false);");
}
/**
* @param listener An optional listener.
* @deprecated Use {@link #WMFileDataSource()} and {@link #addTransferListener(TransferListener)}
*/
@Deprecated
public WMFileDataSource(@Nullable TransferListener listener) {
this();
if (listener != null) {
Log.d(TAG, "listener != null, addTransferListener(listener)");
addTransferListener(listener);
}
}
@Override
public long open(DataSpec dataSpec) throws FileDataSource.FileDataSourceException {
try {
opensignal = true;
uri = dataSpec.uri;
transferInitializing(dataSpec);
file = new RandomAccessFile(dataSpec.uri.getPath(), "r");
suffix = dataSpec.uri.getPath().substring(dataSpec.uri.getPath().lastIndexOf(".")+1);
pos = dataSpec.position;
startpos = dataSpec.position-(dataSpec.position%1024);
bytesRemaining = dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position
: dataSpec.length;
if (pos != file.length()) {
Log.d(TAG, "pos = " + pos + " startpos " + startpos);
if (bytesRemaining <= 512000 - 1024) {
bytesRemaining += pos - startpos;
}
file.seek((pos - (pos % 1024)));
}
Log.d(TAG, "file.open();");
if (bytesRemaining < 0) {
throw new EOFException();
}
} catch (IOException e) {
throw new FileDataSource.FileDataSourceException(e);
}
opened = true;
transferStarted(dataSpec);
return bytesRemaining;
}
private int getMoreData() throws IOException {
if (done) return -1;
ofinish = 0;
ostart = 0;
int readin = readNoDecrypt(ibuffer, 0, ibuffer.length);
if (readin == -1) {
done = true;
Log.d(TAG, "readin == -1, done = true;");
} else {
ofinish = IYDecrypt.DecryptStream(ibuffer, (int) Math.min(ibuffer.length, readin), suffix);
}
return ofinish;
}
@Override
public int read(byte b[], int off, int len) throws IOException {
if (ostart >= ofinish) {
// we loop for new data as the spec says we are blocking
int i = 0;
while (i == 0) i = getMoreData();
if (i == -1) return -1;
if (pos != startpos && opensignal == true){
ostart = (int)(pos - startpos);
opensignal = false;
}
}
if (len <= 0) {
return 0;
}
int available = ofinish - ostart;
if (len < available) available = len;
if (b != null) {
System.arraycopy(ibuffer, ostart, b, off, available);
}
ostart = ostart + available;
return available;
}
public int readNoDecrypt(byte[] buffer, int offset, int readLength) throws FileDataSource.FileDataSourceException {
if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
Log.d(TAG, "bytesRemaining == 0 return C.RESULT_END_OF_INPUT;");
return C.RESULT_END_OF_INPUT;
} else {
int bytesRead;
try {
bytesRead = file.read(buffer, offset, (int) Math.min(bytesRemaining, readLength));
} catch (IOException e) {
throw new FileDataSource.FileDataSourceException(e);
}
if (bytesRead > 0) {
bytesRemaining -= bytesRead;
bytesTransferred(bytesRead);
}
return bytesRead;
}
}
@Override
public @Nullable Uri getUri() {
return uri;
}
@Override
public void close() throws FileDataSource.FileDataSourceException {
uri = null;
Log.d(TAG, "file.close();");
ostart = 0;
ofinish =0;
try {
if (file != null) {
file.close();
}
} catch (IOException e) {
throw new FileDataSource.FileDataSourceException(e);
} finally {
file = null;
if (opened) {
opened = false;
transferEnded();
}
}
}
}
解密算法的jni接口
IYDecrypt.java
public class IYDecrypt {
static {
System.loadLibrary("iydecrypt");
}
//native方法
public static native void test();
public static native int DecryptStream(byte[] data, int size, String key_type);
}
native方法的实现部分
com_android_IYDecryptl.cpp
#define LOG_TAG "iydecrypt"
//#define LOG_NDEBUG 0
#include <jni.h>
#include "utils/Log.h"
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#define KEY_MAX 20
#define CRYPT_BLOCK_LEN (1024)
#define CRYPT_TYPE_ALL (1)
#define CRYPT_TYPE_2 (2)
#define CRYPT_TYPE_64 (64)
const jint lyAesContentLen = 16;
typedef struct keyPairs {
char format[10];
char key[20];
int crypt_type;
}keyPairs_s;
static keyPairs_s keyPairs[KEY_MAX] = {
{"mp36", "6sdfwmq1pu752dqh", CRYPT_TYPE_2},
{"mvh5", "2k6Pd9A8ak7ul4Mv", CRYPT_TYPE_64},
{"hu6d", "2g0kdhsldfmdfjsd", CRYPT_TYPE_ALL},
};
#ifdef __cplusplus
extern "C" {
#endif
#include "aes.h"
jstring charTojstring(JNIEnv* env, const char* pat) {
//定义java String类 strClass
jclass strClass = (env)->FindClass("Ljava/lang/String;");
//获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
//建立byte数组
jbyteArray bytes = (env)->NewByteArray(strlen(pat));
//将char* 转换为byte数组
(env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*) pat);
// 设置String, 保存语言类型,用于byte数组转换至String时的参数
jstring encoding = (env)->NewStringUTF("GB2312");
//将byte数组转换为java String,并输出
return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding);
}
char* jstringToChar(JNIEnv* env, jstring jstr) {
int i = 0;
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
JNIEXPORT jint JNICALL Java_com_wsd_android_IYDecrypt_DecryptStream
(JNIEnv *env, jclass clazz, jbyteArray data, jint num, jstring key_type)
{
jint i = 0;
jint index = 0;
jint counter = 0;
jbyte *charArray;
AES_KEY key;
char *pKey = NULL;
jbyteArray jbyArray;
FILE *tagfp = NULL;
pKey = jstringToChar(env, key_type);
if (pKey == NULL) {
ALOGE("Java_com_wsd_android_IYDecrypt_DecryptStream jstringToChar fail");
return 0;
}
for (i = 0; i < KEY_MAX; i++) {
if (strstr(keyPairs[i].format, pKey) != NULL) {
index = i;
break;
}
}
if (pKey != NULL) {
free(pKey);
pKey = NULL;
}
if (index >= KEY_MAX) {
ALOGE("Java_com_wsd_android_IYDecrypt_DecryptStream not support this decrypt format");
return 0;
}
AES_set_decrypt_key(keyPairs[index].key, strlen(keyPairs[index].key)*8, &key);
charArray = env->GetByteArrayElements(data, NULL);
if (charArray == NULL) {
ALOGE("get array fail");
return 0;
}
for(i=0; i<num-(num%lyAesContentLen);i+=lyAesContentLen ) {
if(counter% keyPairs[index].crypt_type == 0)
AES_decrypt(reinterpret_cast<unsigned char *>(&charArray[i]),reinterpret_cast<unsigned char *>(&charArray[i]), &key);
counter++;
}
env->SetByteArrayRegion(data, 0, num, reinterpret_cast<jbyte *>(charArray));
env->ReleaseByteArrayElements(data, charArray, 0);
return num;
}
JNIEXPORT jint JNICALL Java_com_wsd_android_IYDecrypt_test
(JNIEnv *env, jclass clazz)
{
ALOGD("Java_com_wsd_android_IYDecrypt_test");
return 0;
}
#ifdef __cplusplus
}
#endif
aes.h
#ifndef HEADER_AES_H
#define HEADER_AES_H
#ifdef OPENSSL_NO_AES
#error AES is disabled.
#endif
#include <stddef.h>
#define AES_ENCRYPT 1
#define AES_DECRYPT 0
#define FULL_UNROLL 1
/* Because array size can't be a const in C, the following two are macros.
Both sizes are in bytes. */
#define AES_MAXNR 14
#define AES_BLOCK_SIZE 16
/* This should be a hidden type, but EVP requires that the size be known */
struct aes_key_st {
#ifdef AES_LONG
unsigned long rd_key[4 *(AES_MAXNR + 1)];
#else
unsigned int rd_key[4 *(AES_MAXNR + 1)];
#endif
int rounds;
};
typedef struct aes_key_st AES_KEY;
int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
AES_KEY *key);
int AES_set_decrypt_key(char *userKey, const int bits,
AES_KEY *key);
void AES_encrypt(const unsigned char *in, unsigned char *out,
const AES_KEY *key);
void AES_decrypt(const unsigned char *in, unsigned char *out,
const AES_KEY *key);
/* from aes_locl.h */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_X64))
# define SWAP(x) (_lrotl(x, 8) & 0x00ff00ff | _lrotr(x, 8) & 0xff00ff00)
# define GETU32(p) SWAP(*((u32 *)(p)))
# define PUTU32(ct, st) { *((u32 *)(ct)) = SWAP((st)); }
#else
# define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3]))
# define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); }
#endif
#ifdef AES_LONG
typedef unsigned long u32;
#else
typedef unsigned int u32;
#endif
typedef unsigned short u16;
typedef unsigned char u8;
#define MAXKC (256/32)
#define MAXKB (256/8)
#define MAXNR 14
#endif /* !HEADER_AES_H */
Android.mk
vendor/apps/Android.mk
include $(CLEAR_VARS)
LOCAL_MODULE := libiydecrypt
LOCAL_MODULE_TAGS := optional
LOCAL_CFLAGS := -DANDROID_NDK
LOCAL_SRC_FILES := IYDecrypt/com_android_IYDecryptl.cpp \
IYDecrypt/aes_core.c
LOCAL_LDLIBS := -ldl -llog
LOCAL_C_INCLUDES += $(LOCAL_PATH)/IYDecrypt
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libbinder \
libutils \
libcutils \
libc
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)