ijkplayer编译生成aar,支持https,rtsp,录制与截图
ijkplayer编译,支持https,rtsp,直播录制与截图功能
直接使用可在android-aar目录下自取aar
有其它需求可拉取源码后再次编译demo地址:https://github.com/zzhengzhe/ijkplayer.git
ijk编译流程
1.安装git 和 yasm
sudo apt-get install git
sudo apt-get install yasm
2.配置sdk和ndk环境变量
注意:ndk使用最新r23b版本后期编译报错,建议直接下载r10e版本https://dl.google.com/android/repository/android-ndk-r10e-linux-x86_64.zip
打开.bashrc文件,配置环境
验证ndk配置是否成功:ndk-build -v
3.获取ijkplayer源码
git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
cd ijkplayer-android
git checkout -B latest k0.8.8
4. 初始化
./init-android.sh
5.修改module-lite.sh支持rtsp
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=rtp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtsp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=mjpeg"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=mjpeg"
cd config
rm module.sh
ln -s module-lite.sh module.sh
source module.sh
6.增加录制和截图功能
路径:ijkmedia/ijkplayer/Android.mk
//修改 //LOCAL_LDLIBS += -llog -landroid LOCAL_LDLIBS += -llog -landroid -ljnigraphics
路径:ijkmedia/ijkplayer/android/ijkplayer_jni.c
#include "ijksdl/android/ijksdl_android_jni.h" //新增 #include <android/bitmap.h>
//新增static jboolean IjkMediaPlayer_getCurrentFrame(JNIEnv *env, jobject thiz, jobject bitmap) {jboolean retval = JNI_TRUE;IjkMediaPlayer *mp = jni_get_media_player(env, thiz);JNI_CHECK_GOTO(mp, env, NULL, "mpjni: getCurrentFrame: null mp", LABEL_RETURN);uint8_t *frame_buffer = NULL;if (0 > AndroidBitmap_lockPixels(env, bitmap, (void **)&frame_buffer)) {(*env)->ThrowNew(env, "java/io/IOException", "Unable to lock pixels.");return JNI_FALSE;}ijkmp_get_current_frame(mp, frame_buffer);if (0 > AndroidBitmap_unlockPixels(env, bitmap)) {(*env)->ThrowNew(env, "java/io/IOException", "Unable to unlock pixels.");return JNI_FALSE;}LABEL_RETURN:ijkmp_dec_ref_p(&mp);return retval; }static jint IjkMediaPlayer_startRecord(JNIEnv *env, jobject thiz,jstring file) {jint retval = 0;IjkMediaPlayer *mp = jni_get_media_player(env, thiz);JNI_CHECK_GOTO(mp, env, NULL, "mpjni: startRecord: null mp", LABEL_RETURN);const char *nativeString = (*env)->GetStringUTFChars(env, file, 0);retval = ijkmp_start_record(mp,nativeString);LABEL_RETURN:ijkmp_dec_ref_p(&mp);return retval; }static jint IjkMediaPlayer_stopRecord(JNIEnv *env, jobject thiz) {jint retval = 0;IjkMediaPlayer *mp = jni_get_media_player(env, thiz);JNI_CHECK_GOTO(mp, env, NULL, "mpjni: stopRecord: null mp", LABEL_RETURN);retval = ijkmp_stop_record(mp);LABEL_RETURN:ijkmp_dec_ref_p(&mp);return retval; }
{ "getDuration", "()J", (void *) IjkMediaPlayer_getDuration },//新增{ "getCurrentFrame", "(Landroid/graphics/Bitmap;)Z", (void *) IjkMediaPlayer_getCurrentFrame },{ "startRecord", "(Ljava/lang/String;)I", (void *) IjkMediaPlayer_startRecord },{ "stopRecord", "()I", (void *) IjkMediaPlayer_stopRecord },
路径:ijkmedia/ijkplayer/ff_ffplay.c
//修改 static int read_thread(void *arg) {/* offset should be seeked*/if (ffp->seek_at_start > 0) {ffp_seek_to_l(ffp, (long)(ffp->seek_at_start));}for (;;) { //插入开始if (!ffp->is_first && pkt->pts == pkt->dts) { // 获取开始录制前dts等于pts最后的值,用于if (pkt->stream_index == AVMEDIA_TYPE_AUDIO) {ffp->start_a_pts = pkt->pts;ffp->start_a_dts = pkt->dts;}}if (pkt->stream_index == AVMEDIA_TYPE_VIDEO) {if (!ffp->is_first) {ffp->start_v_pts = pkt->pts;ffp->start_v_dts = pkt->dts;// printf("set start video start_pts: %lld +++++ start_dts: %lld\n", ffp->start_v_pts, ffp->start_v_dts);}}if (pkt->stream_index == AVMEDIA_TYPE_VIDEO) {// printf("video pts: %lld +++++ dts: %lld\n", pkt->pts, pkt->dts);} #pragma mark - 录制插入if (ffp->is_record) { // 可以录制时,写入文件if (0 != ffp_record_file(ffp, pkt)) {ffp->record_error = 1;ffp_stop_recording_l(ffp);}} //插入结束 if (is->abort_request)break; #ifdef FFP_MERGE}
//新增void ffp_get_current_frame_l(FFPlayer *ffp, uint8_t *frame_buf) {ALOGD("=============>start snapshot\n");VideoState *is = ffp->is;Frame *vp;int i = 0, linesize = 0, pixels = 0;uint8_t *src;vp = &is->pictq.queue[is->pictq.rindex];int height = vp->bmp->h;int width = vp->bmp->w;ALOGD("=============>%d X %d === %d\n", width, height, vp->bmp->pitches[0]);// copy data to bitmap in java codelinesize = vp->bmp->pitches[0];src = vp->bmp->pixels[0];pixels = width * 4;for (i = 0; i < height; i++) {memcpy(frame_buf + i * pixels, src + i * linesize, pixels);}ALOGD("=============>end snapshot\n"); }int ffp_start_recording_l(FFPlayer *ffp, const char *file_name) {assert(ffp);VideoState *is = ffp->is;ffp->m_ofmt_ctx = NULL;ffp->m_ofmt = NULL;ffp->is_record = 0;ffp->record_error = 0;if (!file_name || !strlen(file_name)) { // 没有路径av_log(ffp, AV_LOG_ERROR, "filename is invalid");goto end;}if (!is || !is->ic|| is->paused || is->abort_request) { // 没有上下文,或者上下文已经停止av_log(ffp, AV_LOG_ERROR, "is,is->ic,is->paused is invalid");goto end;}if (ffp->is_record) { // 已经在录制av_log(ffp, AV_LOG_ERROR, "recording has started");goto end;}// 初始化一个用于输出的AVFormatContext结构体avformat_alloc_output_context2(&ffp->m_ofmt_ctx, NULL, "mp4", file_name);if (!ffp->m_ofmt_ctx) {av_log(ffp, AV_LOG_ERROR, "Could not create output context filename is %s\n", file_name);goto end;}ffp->m_ofmt = ffp->m_ofmt_ctx->oformat;for (int i = 0; i < is->ic->nb_streams; i++) {// 对照输入流创建输出流通道AVStream *in_stream = is->ic->streams[i];AVStream *out_stream = avformat_new_stream(ffp->m_ofmt_ctx, in_stream->codec->codec);if (!out_stream) {av_log(ffp, AV_LOG_ERROR, "Failed allocating output stream\n");goto end;}// 将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体av_log(ffp, AV_LOG_DEBUG, "in_stream->codec;%@\n", in_stream->codec);if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {av_log(ffp, AV_LOG_ERROR, "Failed to copy context from input to output stream codec context\n");goto end;}out_stream->codec->codec_tag = 0;if (ffp->m_ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;}}av_dump_format(ffp->m_ofmt_ctx, 0, file_name, 1);// 打开输出文件if (!(ffp->m_ofmt->flags & AVFMT_NOFILE)) {if (avio_open(&ffp->m_ofmt_ctx->pb, file_name, AVIO_FLAG_WRITE) < 0) {av_log(ffp, AV_LOG_ERROR, "Could not open output file '%s'", file_name);goto end;}}// 写视频文件头if (avformat_write_header(ffp->m_ofmt_ctx, NULL) < 0) {av_log(ffp, AV_LOG_ERROR, "Error occurred when opening output file\n");goto end;}ffp->is_record = 1;ffp->record_error = 0;pthread_mutex_init(&ffp->record_mutex, NULL);return 0; end:ffp->record_error = 1;return -1; }int ffp_record_file(FFPlayer *ffp, AVPacket *packet) {assert(ffp);VideoState *is = ffp->is;int ret = 0;AVStream *in_stream;AVStream *out_stream;if (ffp->is_record) {if (packet == NULL) {ffp->record_error = 1;av_log(ffp, AV_LOG_ERROR, "packet == NULL");return -1;}AVPacket *pkt = (AVPacket *)av_malloc(sizeof(AVPacket)); // 与看直播的 AVPacket分开,不然卡屏av_new_packet(pkt, 0);if (0 == av_packet_ref(pkt, packet)) {pthread_mutex_lock(&ffp->record_mutex);if (!ffp->is_first) { // 录制的第一帧,时间从0开始ffp->is_first = 1;pkt->pts = 0;pkt->dts = 0;} else { // 之后的每一帧都要减去,点击开始录制时的值,这样的时间才是正确的//pkt->pts = abs(pkt->pts - ffp->start_pts);//pkt->dts = abs(pkt->dts - ffp->start_dts);if (pkt->stream_index == AVMEDIA_TYPE_AUDIO) {pkt->pts = llabs(pkt->pts - ffp->start_a_pts);pkt->dts = llabs(pkt->dts - ffp->start_a_dts);}else if (pkt->stream_index == AVMEDIA_TYPE_VIDEO) {pkt->pts = pkt->dts = llabs(pkt->dts - ffp->start_v_dts);}}in_stream = is->ic->streams[pkt->stream_index];out_stream = ffp->m_ofmt_ctx->streams[pkt->stream_index];// 转换PTS/DTSpkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base);pkt->pos = -1;// 写入一个AVPacket到输出文件if ((ret = av_interleaved_write_frame(ffp->m_ofmt_ctx, pkt)) < 0) {av_log(ffp, AV_LOG_ERROR, "Error muxing packet\n");}av_packet_unref(pkt);pthread_mutex_unlock(&ffp->record_mutex);} else {av_log(ffp, AV_LOG_ERROR, "av_packet_ref == NULL");}}return ret; }int ffp_record_isfinished_l(FFPlayer *ffp) {return 0; }int ffp_stop_recording_l(FFPlayer *ffp) {assert(ffp);if (ffp->is_record) {ffp->is_record = 0;pthread_mutex_lock(&ffp->record_mutex);if (ffp->m_ofmt_ctx != NULL) {av_write_trailer(ffp->m_ofmt_ctx);if (ffp->m_ofmt_ctx && !(ffp->m_ofmt->flags & AVFMT_NOFILE)) {avio_close(ffp->m_ofmt_ctx->pb);}avformat_free_context(ffp->m_ofmt_ctx);ffp->m_ofmt_ctx = NULL;ffp->is_first = 0;}pthread_mutex_unlock(&ffp->record_mutex);pthread_mutex_destroy(&ffp->record_mutex);av_log(ffp, AV_LOG_DEBUG, "stopRecord ok\n");} else {av_log(ffp, AV_LOG_ERROR, "don't need stopRecord\n");}return 0; }
路径:ijkmedia/ijkplayer/ff_ffplay.h
void ffp_set_property_int64(FFPlayer *ffp, int id, int64_t value); //新增 int ffp_start_recording_l(FFPlayer *ffp, const char *file_name); int ffp_stop_recording_l(FFPlayer *ffp); int ffp_record_file(FFPlayer *ffp, AVPacket *packet); void ffp_get_current_frame_l(FFPlayer *ffp, uint8_t *frame_buf); int ffp_record_isfinished_l(FFPlayer *ffp);
路径:ijkmedia/ijkplayer/ff_ffplay_def.h
int render_wait_start;//新增AVFormatContext *m_ofmt_ctx; // 用于输出的AVFormatContext结构体AVOutputFormat *m_ofmt;pthread_mutex_t record_mutex; // 锁int is_record; // 是否在录制int record_error;int is_first; // 第一帧数据int64_t start_v_pts; // 开始录制时pts 视频int64_t start_v_dts; // 开始录制时dts 视频int64_t start_a_pts; // 开始录制时pts 音频int64_t start_a_dts; // 开始录制时dts 音频
路径:ijkmedia/ijkplayer/ijkplayer.c
//新增static void ijkmp_get_current_frame_l(IjkMediaPlayer *mp, uint8_t *frame_buf) {ffp_get_current_frame_l(mp->ffplayer, frame_buf); }void ijkmp_get_current_frame(IjkMediaPlayer *mp, uint8_t *frame_buf) {assert(mp);pthread_mutex_lock(&mp->mutex);ijkmp_get_current_frame_l(mp, frame_buf);pthread_mutex_unlock(&mp->mutex); }static int ijkmp_start_recording_l(IjkMediaPlayer *mp,const char *filePath) { // av_log(mp->ffplayer,AV_LOG_WARING,"cjz ijkmp_start_recording_l filePath %s",filePath);return ffp_start_recording_l(mp->ffplayer,filePath); }int ijkmp_start_record(IjkMediaPlayer *mp,const char *filePath) {assert(mp);pthread_mutex_lock(&mp->mutex);av_log(mp->ffplayer,AV_LOG_WARNING,"cjz ijkmp_start_recording");int retval = ijkmp_start_recording_l(mp,filePath);pthread_mutex_unlock(&mp->mutex);return retval; }static int ijkmp_stop_recording_l(IjkMediaPlayer *mp) {return ffp_stop_recording_l(mp->ffplayer); } int ijkmp_stop_record(IjkMediaPlayer *mp) {assert(mp);pthread_mutex_lock(&mp->mutex);av_log(mp->ffplayer,AV_LOG_WARNING,"cjz ijkmp_stop_recording");int retval = ijkmp_stop_recording_l(mp);pthread_mutex_unlock(&mp->mutex);return retval; }static int ijkmp_isRecordFinished_l(IjkMediaPlayer *mp) {return ffp_record_isfinished_l(mp->ffplayer); }int ijkmp_isRecordFinished(IjkMediaPlayer *mp) {assert(mp);pthread_mutex_lock(&mp->mutex);av_log(mp->ffplayer,AV_LOG_WARNING,"cjz ijkmp_isRecordFinished ");int retval = ijkmp_isRecordFinished_l(mp);pthread_mutex_unlock(&mp->mutex);return retval; }int ijkmp_isRecording(IjkMediaPlayer *mp) {return mp->ffplayer->is_record; }
7.支持Https
cd ..
./init-android-openssl.sh
8.清除
cd android/contrib
./compile-openssl.sh clean
./compile-ffmpeg.sh clean
9.编译openssl
./compile-openssl.sh all
10.编译ffmpeg
./compile-ffmpeg.sh all
11.编译ijkplayer
cd ..
./compile-ijk.sh all
aar打包流程
编译成功后会在android目录下生成一个ijkplayer的工程,导入AS
右键项目 Open Module Settings,点击减号移除ijkplayer-example和ijkplayer-java以外的依赖
增加需要支持的.so,打开ijkplayer-java/src/main/,新建一个libs文件夹,同时打开ijkplayer-xxx/main/libs,把里面的armeabi-v7a,arm64-v8a,armeabi三个文件夹复制到ijkplayer-java的libs文件夹下
修改IMediaPlayer
//新增int startRecord(String file);int stopRecord();boolean getCurrentFrame(Bitmap bitmap);
修改IjkMediaPlayer/AndroidMediaPlayer/MediaPlayerProxy,新增native方法
@Overridepublic native int startRecord(String file);@Overridepublic native int stopRecord();@Overridepublic native boolean getCurrentFrame(Bitmap bitmap);
修改下ijkplayer-java的build.gradle文件,编译arr
gradle -- ijkplayer-java – Tasks – build -- assemble
ijkplayer编译生成aar,支持https,rtsp,录制与截图相关推荐
- IJKPlayer播放器支持https流程
前言: 笔者最近一段时间在搞视频直播相关的东西,虽然很早以前,也做过基于腾讯云.Anychat.容联云等几个第三方的实时视频直播,但是当时的播放器完全采用了第三方的东西,本次则是使用的ijkplaye ...
- ijkplayer 视频播放器集成(附带支持https的so库)
前言 做一款视频播放器需要多久?有了ijkplayer只需要简单几步就能快速搞出来一个功能强大播放器. github链接:ijkplayer Android: Gradlerequired allpr ...
- loadrunner支持https协议的操作方法-经验总结
问题:用户portal支持https协议,用loadrunner录制登陆脚本时发现未录制到用户名和密码 录制到的脚本如下: login() { lr_think_time(10); web_url(& ...
- 新功能:阿里云负载均衡SLB支持HTTPS虚拟主机功能(SNI)
Greeting 大家好,很高兴告诉大家,阿里云负载均衡SLB支持HTTPS虚拟主机(单VIP多证书功能-SNI),目前已经在所有地域开放,欢迎大家使用.具体内容请大家观看视频. 温馨提醒:视频杀流量 ...
- (0009) iOS 开发之友盟统计分析SDK已全面支持HTTPS的更新
iOS开发技术分享群 147787076 [友盟+]友盟统计分析SDK已全面支持HTTPS,请IOS开发者及时升级文档(http://dev.umeng.com/analytics/ios-doc/i ...
- curl提示不支持https协议解决方法
curl提示不支持https协议解决方法 参考文章: (1)curl提示不支持https协议解决方法 (2)https://www.cnblogs.com/biaopei/p/8669810.html ...
- go get 代理 找不到包_初步看看Go1.10 支持 HTTPS 代理
Go1.10 支持 HTTPS 代理 Go1.9 出来后 6 个多月的时间,Go1.10 就被发布.新版本带来大大小小的变化(发行说明),但是我想谈谈有关 net/http 包的改变.1.10 版本支 ...
- TurboMail邮件系统支持HTTPS双向认证
2019独角兽企业重金招聘Python工程师标准>>> HTTP单向认证已经被普遍应用,而对企业邮箱安全保密要求更加严格的企事业单位,例如国家保密局等单位,为了达到闭环的安全加密要求 ...
- php cur https,解决php-curl不支持https问题
主要原因是因为之前安装的 curl 不支持 https 协议,导致 php-curl 也不支持 https 协议 #/server/app/services/bin/curl -V 查看curl ...
最新文章
- 科研费4年翻3倍,全球科研队伍突破8000人,滴滴致力打造出行领域核心技术
- oracle中的赋权
- 亚马逊消费者业务宣布永久关闭 Oracle 数据库,去O新进展;华为发布最新5G全系列解决方案;苹果正研究新设备“智能戒指”……...
- Linux Shell编程之别名和常用快捷键
- laravel接口开发JWTAuth
- django 1.8 官方文档翻译: 3-4-1 基于类的视图
- extract()函数:用于从一个date或者interval类型中截取到特定的部分
- [转载] numpy功能快速查找
- 2021L3HCTF luuuuua Writeup
- 解决IAR printf函数输出中文字符乱码问题
- Linked Data_百度百科
- module java.base does not “opens java.lang“ to module spring.core
- TTL传输中过期问题导致网站打不开
- android 方向传感器,10.11 传感器专题(2)——方向传感器
- 推荐一个赚钱平台,100%能赚钱(合法 长久 稳定)
- rdkafka线程过多_我是如何处理大并发量订单处理的 KafKa部署总结
- linux top VIRT RES SHR SWAP DATA内存参数详解
- Java子类访问父类私有变量的思考
- 复频域求零输入响应_信号处理的复频域分析方法推荐
- asp.net酒店餐饮管理系统