陕西做网站的公司地址,软件关键词排名,wordpress关闭前端公共库,软件技术专业里有网站开发吗想对播放的音视频进行加密#xff0c;防止资源被盗用#xff0c;该怎么办呢#xff1f;
这篇文章从自定义协议的角度来提供一中实现思路。在 ijkplayer 的基础上#xff0c;通过实现自定义协议对文件进行解密。边解边播#xff0c;以此为基础#xff0c;还可以实现在线资…
想对播放的音视频进行加密防止资源被盗用该怎么办呢
这篇文章从自定义协议的角度来提供一中实现思路。在 ijkplayer 的基础上通过实现自定义协议对文件进行解密。边解边播以此为基础还可以实现在线资源边下载边解密边播放。
结合 ijkplayer 源码阅读本文效果最佳。
FFmpeg 文件协议
ffmpeg 中定义了 URLProtocol 对接入 ffmpeg 中的各种协议进行了统一的抽象这里可以理解为 C 中的抽象基类并基于此抽象协议实现了 file、http、ftp、cache 等许多不同的具体文件传输协议。 而在 libavformat 模块中的 avio.c aviobuf.c 两个文件则是在 ffmpeg 对文件传输协议抽象的基础上进行的封装使对文件的操作能够在 ffmpeg 中其他对文件协议细节不关心的地方使用。这就好比是面向对象中的模版模式。 ffmpeg 中的文件协议简单讲就是这样更深入复杂的我也还没具体研究本文中也用不到。
URLProtocol 的部分代码如下这里我只留下了最常用的一部分。URLProtocol 中定义了很多函数指针在 avio.c 的模板模式代码中用到。 代码解读
复制代码
typedef struct URLProtocol { const char *name; int (*url_open)( URLContext *h, const char *url, int flags); int (*url_read)( URLContext *h, unsigned char *buf, int size); int (*url_write)(URLContext *h, const unsigned char *buf, int size); int64_t (*url_seek)( URLContext *h, int64_t pos, int whence); int (*url_close)(URLContext *h); int priv_data_size; const AVClass *priv_data_class; } URLProtocol;
通过实现 URLProtocol 中的函数指针就完成了一个可以用在 ffmpeg 中的文件协议。比如我找了一番 ffmpeg 中一个比较简单的文件协议从实现上可以看出这个 bluray 在ffmpeg 中是一个只读协议。 代码解读
复制代码
const URLProtocol ff_bluray_protocol { .name bluray, .url_close bluray_close, .url_open bluray_open, .url_read bluray_read, .url_seek bluray_seek, .priv_data_size sizeof(BlurayContext), .priv_data_class bluray_context_class, };
ffmpeg 中通过一个列表保存了所有实现了的文件协议进行各种文件操作的第一步就是先根据 urlfilename找到对应的 URLProtocol。这段代码中通过匹配 url 中的 scheme 字符串找到并返回对应的 protocol 指针。 代码解读
复制代码
static const struct URLProtocol *url_find_protocol(const char *filename) { // ...... const URLProtocol **protocols ffurl_get_protocols(NULL, NULL); if (!protocols) return NULL; for (i 0; protocols[i]; i) { const URLProtocol *up protocols[i]; if (!strcmp(proto_str, up-name)) { av_freep(protocols); return up; } } // ...... }
如果对 ffmpeg 有一定的了解可能会知道 ffmpeg 中的 Codec 还有 avcodec_register。FFmpeg 通过接口 avcodec_register 在运行时动态添加编解码器。但是却没有类似的 avformat_register 或者 av_urlprotocol_register 来实现运行时动态注册自定义的 protocol。为什么不实现这个的原因没有研究过所以不能瞎吹。但是我们可以改 ffmpeg 源代码呀。
ijkplayer 的作者就改了 ffmpeg 新增了几个 URLProtocol 并加入到了默认的 protocols 数组中这些新增的 protocols 在 ffmpeg 源码中默认是空的实现。但是在 ffmpeg 中预留了接口可以在 ijkplayer 中使用后期实现的 protocol 替换这个空的 protocol。相当于添加了作用类似于 av_urlprotocol_register 但是有一定限制的接口。 ijkmediadatasource 协议实现
在 ijkplayer 项目的 ijkmediadatasource.c 源文件中实现了一种 URLProtocol 代码解读
复制代码
URLProtocol ijkimp_ff_ijkmediadatasource_protocol { .name ijkmediadatasource, .url_open2 ijkmds_open, .url_read ijkmds_read, .url_seek ijkmds_seek, .url_close ijkmds_close, .priv_data_size sizeof(Context), .priv_data_class ijkmediadatasource_context_class, };
在每个具体的函数指针实现中又通过 J4A 生成的胶水代码去调用到 java 层的代码。 进一步解释一下 setDataSource 的时候调用到这一块代码callback 已经是一个具体的 java 对象了并且是一个 tv/danmaku/ijk/media/player/misc/IMediaDataSource 接口的具体实现。 代码解读
复制代码
IjkMediaPlayer_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject callback) { nativeMediaDataSource jni_set_media_data_source(env, thiz, callback); snprintf(uri, sizeof(uri), ijkmediadatasource:%PRId64, nativeMediaDataSource); retval ijkmp_set_data_source(mp, uri); }
jni_set_media_data_source 函数新建一个 NDK 环境对 callback 这个 jobject 的全局引用并把引用作为 intptr_t 类型保存在 IjkMediaPlayer 的 mNativeMediaDataSource 字段中方便后面再次使用以及最后需要close 是用到。 通过这样的转换之后url 进入 ffmpeg avformat 逻辑中就成了 “ijkmediadatasource:2234234290 这样的形式。 再通过 url_find_protocol 找到 ijkimp_ff_ijkmediadatasource_protocol 所有的对这个文件协议的操作又回到 ijkplayer 的代码中了。 在 ijkmds_open 中只需要把 intptr_t 的变量转换成 jobject 就行不必实际去打开某个文件。 ijkmds_read 、 ijkmds_seek 函数通过 J4A 去调用 IMediaDataSource 接口的 readAt 方法。 readAt 方法多了 pos 参数所以 read 和 seek 都可以通过 readAt 实现 ijkmds_close 调用 IMediaDataSource 接口的 close 方法并释放 NDK 环境中的 jobject 全局引用。
通过这一层套一层的接口定义实现、struct 函数指针定义实现 java 代码中设置的 dataSource 在 ijkplayer 、ffmpeg 中打了个转之后最终又回到 java 代码中。 jni4android 自动生成代码
jni4android 是 B 站出品的开源项目 github.com/bilibili/jn…。能够根据简单的 java 接口描述代码生成 ndk 胶水代码方便在 ndk c/c 环境中调用 java 代码。 ijkmediadatasource.c 源文件中 JNI 相关调用的代码都是 j4a 自动生成的。按照 jni4android 中 readme 编译好 j4a 之后结合 ijkplayer/ijkmedia/j4a 中的 makefile 文件就可以快速掌握 j4a 在 ijkplayer 中的作用和使用方法了。 加密和解密
前面代码分析中解释了 ijkplayer 中自定义文件协议到底是怎么一回事以及其中的函数调用过程怎么样的。 分析完了终于可以开始代码敲起来。我们先对一个视频文件加密然后实现解密的文件协议并在播放中使用。
这里采用古老的凯撒加解密算法加密过程就是每个 byte 加个数字解密过程就是每个 byte 减去个数字。相当简单很容易暴力破解这里仅作为示例参考。
加密过程如下(截取部分代码) 代码解读
复制代码
func main() { ibuf : bufio.NewReader(inputfile) obuf : bufio.NewWriter(outputfile) buf : make([]byte, 128) for { n, err : ibuf.Read(buf) if err nil { for index : 0; index n; index { buf[index] buf[index] byte(cck) } obuf.Write(buf[:n]) } else if err io.EOF { break } } obuf.Flush() }
解密的 java IMediaDataSource 实现如下这里只给出关键部分其他的和项目中 FileMediaDataSource 一样。 代码解读
复制代码
public class CCFileMediaDataSource implements IMediaDataSource { Override public int readAt(long position, byte[] buffer, int offset, int size) throws IOException { if (mFile.getFilePointer() ! position) mFile.seek(position); if (size 0) return 0; int s mFile.read(buffer, 0, size); for (int i 0; i s; i) { buffer[i] (byte)(buffer[i] - 10); } return s; } }
什么情况下会用到 CCFileMediaDataSource 呢 简单粗暴把项目中 IjkVieoView.java 用到 FileMediaDataSource 的地方都改成 CCFileMediaDataSource其实也没几处然后别忘了在 demo 中设置选项里选中使用MediaDataSource。 IMediaDataSource 其他实现
RandomAccessFile 实现的 IMediaDataSource 支持本地保存的文件。 代码解读
复制代码
public class FileMediaDataSource implements IMediaDataSource { private RandomAccessFile mFile; private long mFileSize; public FileMediaDataSource(File file) throws IOException { mFile new RandomAccessFile(file, r); mFileSize mFile.length(); } Override public int readAt(long position, byte[] buffer, int offset, int size) throws IOException { if (mFile.getFilePointer() ! position) mFile.seek(position); if (size 0) return 0; return mFile.read(buffer, 0, size); } Override public long getSize() throws IOException { return mFileSize; } Override public void close() throws IOException { mFileSize 0; mFile.close(); mFile null; } }
InputStream 实现的 IMediaDataSource可以用于播放 asset 资源文件 代码解读
复制代码
public class StreamDataSource implements IMediaDataSource { private InputStream mIs; private long mPosition 0; public StreamDataSource(InputStream mIs) { this.mIs mIs; } Override public int readAt(long position, byte[] buffer, int offset, int size) throws IOException { if (size 0) return size; if (mPosition ! position) { mIs.reset(); mPosition mIs.skip(position); } int length mIs.read(buffer, offset, size); mPosition length; return length; } Override public long getSize() throws IOException { return mIs.available(); } Override public void close() throws IOException { if (mIs ! null) mIs.close(); mIs null; } } 代码解读
复制代码
AssetManager assetManager mContext.getAssets(); InputStream is assetManager.open(asset_file_path, AssetManager.ACCESS_RANDOM); mIjkMediaPlayer.setDataSource(new StreamDataSource(is));