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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值