首先来看一副图,用来记念对视频领域作出贡献的雷神:java
固然在这个图片里面的decode不是必须的,Filter 模块自己是一个很是独立的模块,但由于相关的程序,给人形成了他必需要依赖于decoder或者encoder来工做。android
OK 很少说,先看看内部实现代码:api
[cpp] view plain copy架构
- #ifndef __CAREYE_PUBLIC_H__
- #define __CAREYE_PUBLIC_H__
- #define __STDC_CONSTANT_MACROS
- #ifdef _WIN32
- #define CE_API __declspec(dllexport)
- #define CE_APICALL __stdcall
- #else
- #define CE_API
- #define CE_APICALL
- #endif
- #ifndef _WIN32
- #define _ANDROID_
- #endif
- #ifdef _ANDROID_
- #include <android/log.h>
- #define CarEyeLog(...) __android_log_print(ANDROID_LOG_DEBUG, "Car-eye-ffmpeg", __VA_ARGS__)
- #else
- #define CarEyeLog printf
- #endif
- typedef struct _CarEye_YUVFrame_
- {
- // Y份量数据存储区
- unsigned char *Y;
- // Y份量数据字节数
- int YSize;
- // U份量数据存储区
- unsigned char *U;
- // U份量数据字节数
- int USize;
- // V份量数据存储区
- unsigned char *V;
- // V份量数据字节数
- int VSize;
- }CarEye_YUVFrame;
- //error number
- #define NO_ERROR 0
- #define PARAMTER_ERROR 1
- #define NULL_MEMORY 2
- #define MAX_FILTER_DESCR 512
- #endif
[cpp] view plain copyapp
- #ifndef __CAREYE_FILTER_INTERFACE_H__
- #define __CAREYE_FILTER_INTERFACE_H__
- #define MAX_STRING_LENGTH 1024
- #define MAX_FILE_NAME 64
- // OSD水印结果定义
- typedef struct _CarEye_OSDParam_
- {
- int width;
- int height;
- int fps;
- // 起始X轴坐标
- int X;
- // 起始Y轴坐标
- int Y;
- // 字体大小
- int FontSize;
- // 16进制的RGB颜色值,如绿色:0x00FF00
- unsigned int FontColor;
- // 水印透明度 0~1
- float Transparency;
- // 水印内容
- char SubTitle[MAX_STRING_LENGTH];
- // 字体名称,字体文件放到库的同目录下,如“arial.ttf”
- char FontName[MAX_FILE_NAME];
- }CarEye_OSDParam;
- typedef struct
- {
- AVFrame *VFrame;
- // 编码后的音频帧
- CarEye_OSDParam para;
- void* handle;
- }CarEyeFilter;
- #ifdef __cplusplus
- extern "C"
- {
- #endif
- /*
- * Comments: 打开水印资源
- * Param aEncoder: 编码器对象句柄
- * Param aParam: 水印参数
- * @Return int 是否成功,0成功,其余失败
- */
- CE_API int CE_APICALL CarEye_OpenOsd(CarEyeFilter* pFilter, CarEye_OSDParam aParam);
- /*
- * Comments: 关闭水印资源
- * Param aDeocoder: 编码器对象
- * @Return int 关闭成功与否 0成功
- */
- CE_API int CE_APICALL CarEye_CloseOsd(CarEyeFilter* pFilter);
- CE_API int CE_APICALL CarEye_add_osd(CarEyeFilter* pFilter, CarEye_YUVFrame *aYuv, CarEye_OSDParam aParam);
- #ifdef __cplusplus
- }
- #endif
- #endif
实现部分代码:dom
[cpp] view plain copyide
- #include "FFVideoFilter.h"
- /*
- * Comments: 打开水印资源
- * Param aEncoder: 编码器对象句柄
- * Param aParam: 水印参数
- * @Return int 是否成功,0成功,其余失败
- */
- CE_API int CE_APICALL CarEye_OpenOsd( CarEyeFilter* pFliter, CarEye_OSDParam aParam)
- {
- if(pFliter==NULL)
- {
- return -PARAMTER_ERROR;
- }
- avfilter_register_all();
- pFliter->VFrame = NULL;
- pFliter->VFrame = av_frame_alloc();
- if(pFliter->VFrame == NULL)
- {
- return -NULL_MEMORY;
- }
- pFliter->VFrame->width = aParam.width;
- pFliter->VFrame->height = aParam.height;
- pFliter->VFrame->pts = 0;
- if (av_image_alloc(pFliter->VFrame->data, pFliter->VFrame->linesize,
- pFliter->VFrame->width, pFliter->VFrame->height,
- AV_PIX_FMT_YUV420P, 16) < 0)
- {
- CarEyeLog("Cannot av_image_alloc\n");
- av_frame_free(&pFliter->VFrame);
- return -NULL_MEMORY;
- }
- pFliter->VFrame->format = AV_PIX_FMT_YUV420P;
- FFVideoFilter *handle = new FFVideoFilter();
- pFliter->handle =(FFVideoFilter*)handle;
- return handle->InitFilters( aParam);
- }
- CE_API int CE_APICALL CarEye_add_osd(CarEyeFilter* pFliter, CarEye_YUVFrame *aYuv,CarEye_OSDParam param)
- {
- if(pFliter==NULL || pFliter->VFrame == NULL)
- {
- return -PARAMTER_ERROR;
- }
- FFVideoFilter* handle = (FFVideoFilter*)pFliter->handle;
- pFliter->VFrame->pts++;
- memcpy(pFliter->VFrame->data[0], aYuv->Y, aYuv->YSize);
- memcpy(pFliter->VFrame->data[1], aYuv->U, aYuv->USize);
- memcpy(pFliter->VFrame->data[2], aYuv->V, aYuv->VSize);
- if (handle->BlendFilters(pFliter->VFrame,param) < 0)
- {
- return -PARAMTER_ERROR;
- }
- memcpy(aYuv->Y, pFliter->VFrame->data[0], aYuv->YSize);
- memcpy(aYuv->U, pFliter->VFrame->data[1], aYuv->USize);
- memcpy(aYuv->V, pFliter->VFrame->data[2], aYuv->VSize);
- return NO_ERROR;
- }
- /*
- * Comments: 关闭水印资源
- * Param aDeocoder: 编码器对象
- * @Return int 关闭成功与否 0 成功
- */
- CE_API int CE_APICALL CarEye_CloseOsd(CarEyeFilter* pFliter)
- {
- FFVideoFilter* handle = (FFVideoFilter*)pFliter->handle;
- if(pFliter->VFrame != NULL)
- {
- av_frame_free(&pFliter->VFrame);
- }
- delete handle;
- return NO_ERROR;
[cpp] view plain copy函数
- #include "FFVideoFilter.h"
- /*
- * Comments: 打开水印资源
- * Param aEncoder: 编码器对象句柄
- * Param aParam: 水印参数
- * @Return int 是否成功,0成功,其余失败
- */
- CE_API int CE_APICALL CarEye_OpenOsd( CarEyeFilter* pFliter, CarEye_OSDParam aParam)
- {
- if(pFliter==NULL)
- {
- return -PARAMTER_ERROR;
- }
- avfilter_register_all();
- pFliter->VFrame = NULL;
- pFliter->VFrame = av_frame_alloc();
- if(pFliter->VFrame == NULL)
- {
- return -NULL_MEMORY;
- }
- pFliter->VFrame->width = aParam.width;
- pFliter->VFrame->height = aParam.height;
- pFliter->VFrame->pts = 0;
- if (av_image_alloc(pFliter->VFrame->data, pFliter->VFrame->linesize,
- pFliter->VFrame->width, pFliter->VFrame->height,
- AV_PIX_FMT_YUV420P, 16) < 0)
- {
- CarEyeLog("Cannot av_image_alloc\n");
- av_frame_free(&pFliter->VFrame);
- return -NULL_MEMORY;
- }
- pFliter->VFrame->format = AV_PIX_FMT_YUV420P;
- FFVideoFilter *handle = new FFVideoFilter();
- pFliter->handle =(FFVideoFilter*)handle;
- return handle->InitFilters( aParam);
- }
- CE_API int CE_APICALL CarEye_add_osd(CarEyeFilter* pFliter, CarEye_YUVFrame *aYuv,CarEye_OSDParam param)
- {
- if(pFliter==NULL || pFliter->VFrame == NULL)
- {
- return -PARAMTER_ERROR;
- }
- FFVideoFilter* handle = (FFVideoFilter*)pFliter->handle;
- pFliter->VFrame->pts++;
- memcpy(pFliter->VFrame->data[0], aYuv->Y, aYuv->YSize);
- memcpy(pFliter->VFrame->data[1], aYuv->U, aYuv->USize);
- memcpy(pFliter->VFrame->data[2], aYuv->V, aYuv->VSize);
- if (handle->BlendFilters(pFliter->VFrame,param) < 0)
- {
- return -PARAMTER_ERROR;
- }
- memcpy(aYuv->Y, pFliter->VFrame->data[0], aYuv->YSize);
- memcpy(aYuv->U, pFliter->VFrame->data[1], aYuv->USize);
- memcpy(aYuv->V, pFliter->VFrame->data[2], aYuv->VSize);
- return NO_ERROR;
- }
- /*
- * Comments: 关闭水印资源
- * Param aDeocoder: 编码器对象
- * @Return int 关闭成功与否 0 成功
- */
- CE_API int CE_APICALL CarEye_CloseOsd(CarEyeFilter* pFliter)
- {
- FFVideoFilter* handle = (FFVideoFilter*)pFliter->handle;
- if(pFliter->VFrame != NULL)
- {
- av_frame_free(&pFliter->VFrame);
- }
- delete handle;
- return NO_ERROR;
- }
在写JNI以前先看下make文件,主要有两个,android.mk用来实现对ffmpeg 库和新增长的外部水印库的编译,application.mk 主要定义编译的全局变量,如参数和架构等,看下怎么编译ffmpeg的动态库的,这里咱们没有用到FFMPEG的avdevice这个库。oop
#APP_ABI := armeabi armeabi-v7a x86 ifeq ($(APP_ABI), x86) LIB_NAME_PLUS := x86 else LIB_NAME_PLUS := armeabi endif LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= avcodec-prebuilt-$(LIB_NAME_PLUS) LOCAL_SRC_FILES:= prebuilt/$(LIB_NAME_PLUS)/libavcodec-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE:= avfilter-prebuilt-$(LIB_NAME_PLUS) LOCAL_SRC_FILES:= prebuilt/$(LIB_NAME_PLUS)/libavfilter-6.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE:= avformat-prebuilt-$(LIB_NAME_PLUS) LOCAL_SRC_FILES:= prebuilt/$(LIB_NAME_PLUS)/libavformat-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avutil-prebuilt-$(LIB_NAME_PLUS) LOCAL_SRC_FILES := prebuilt/$(LIB_NAME_PLUS)/libavutil-55.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swresample-prebuilt-$(LIB_NAME_PLUS) LOCAL_SRC_FILES := prebuilt/$(LIB_NAME_PLUS)/libswresample-2.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swscale-prebuilt-$(LIB_NAME_PLUS) LOCAL_SRC_FILES := prebuilt/$(LIB_NAME_PLUS)/libswscale-4.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) ifeq ($(APP_ABI), x86) TARGET_ARCH:=x86 TARGET_ARCH_ABI:=x86 else LOCAL_ARM_MODE := arm endif LOCAL_MODULE := libffmpegjni LOCAL_SRC_FILES := com_li_sheldon_ffmpeg4android_FFmpegNative.c CarEyeEncoderAPI.cpp FFVideoFilter.cpp CarEyeFilter_interface.cpp LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -lz LOCAL_SHARED_LIBRARIES:= avcodec-prebuilt-$(LIB_NAME_PLUS) \ avfilter-prebuilt-$(LIB_NAME_PLUS) \ avformat-prebuilt-$(LIB_NAME_PLUS) \ avutil-prebuilt-$(LIB_NAME_PLUS) \ swresample-prebuilt-$(LIB_NAME_PLUS) \ swscale-prebuilt-$(LIB_NAME_PLUS) LOCAL_C_INCLUDES += -L$(SYSROOT)/usr/include LOCAL_C_INCLUDES += $(LOCAL_PATH)/include ifeq ($(APP_ABI), x86) LOCAL_CFLAGS := -DUSE_X86_CONFIG else LOCAL_CFLAGS := -DUSE_ARM_CONFIG endif include $(BUILD_SHARED_LIBRARY)
编译后生成libffmpegjni.so, 而后提供JNI的源码给上层调用:测试
[cpp] view plain copy
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include "com_li_sheldon_ffmpeg4android_FFmpegNative.h"
- /* Header for class com_hsb_ffmpeg_FFmpegNative */
- #include "libavcodec/avcodec.h"
- #include "libavcodec/avdct.h"
- #include "libavcodec/avfft.h"
- #include "libavcodec/dirac.h"
- #include "libavcodec/dv_profile.h"
- #include "libavcodec/vaapi.h"
- #include "libavcodec/version.h"
- #include "libavcodec/vorbis_parser.h"
- #include "libavdevice/avdevice.h"
- #include "libavdevice/version.h"
- #include "libavfilter/avfilter.h"
- #include "libavfilter/avfiltergraph.h"
- #include "libavfilter/buffersink.h"
- #include "libavfilter/buffersrc.h"
- #include "libavfilter/version.h"
- #include "libavformat/avformat.h"
- #include "libavformat/avio.h"
- #include "libavformat/version.h"
- #include "libavutil/adler32.h"
- #include "libavutil/aes_ctr.h"
- #include "libavutil/aes.h"
- #include "libavutil/attributes.h"
- #include "libavutil/audio_fifo.h"
- #include "libavutil/avassert.h"
- #include "libavutil/avconfig.h"
- #include "libavutil/avstring.h"
- #include "libavutil/avutil.h"
- #include "libavutil/base64.h"
- #include "libavutil/blowfish.h"
- #include "libavutil/bprint.h"
- #include "libavutil/bswap.h"
- #include "libavutil/buffer.h"
- #include "libavutil/camellia.h"
- #include "libavutil/cast5.h"
- #include "libavutil/channel_layout.h"
- #include "libavutil/common.h"
- #include "libavutil/cpu.h"
- #include "libavutil/crc.h"
- #include "libavutil/des.h"
- #include "libavutil/dict.h"
- #include "libavutil/display.h"
- #include "libavutil/downmix_info.h"
- #include "libavutil/error.h"
- #include "libavutil/eval.h"
- #include "libavutil/ffversion.h"
- #include "libavutil/fifo.h"
- #include "libavutil/file.h"
- #include "libavutil/frame.h"
- #include "libavutil/hash.h"
- #include "libavutil/hmac.h"
- #include "libavutil/imgutils.h"
- #include "libavutil/intfloat.h"
- #include "libavutil/intreadwrite.h"
- #include "libavutil/lfg.h"
- #include "libavutil/log.h"
- #include "libavutil/lzo.h"
- #include "libavutil/macros.h"
- #include "libavutil/mastering_display_metadata.h"
- #include "libavutil/mathematics.h"
- #include "libavutil/md5.h"
- #include "libavutil/mem.h"
- #include "libavutil/motion_vector.h"
- #include "libavutil/murmur3.h"
- #include "libavutil/opt.h"
- #include "libavutil/parseutils.h"
- #include "libavutil/pixdesc.h"
- #include "libavutil/pixelutils.h"
- #include "libavutil/pixfmt.h"
- #include "libavutil/random_seed.h"
- #include "libavutil/rational.h"
- #include "libavutil/rc4.h"
- #include "libavutil/replaygain.h"
- #include "libavutil/ripemd.h"
- #include "libavutil/samplefmt.h"
- #include "libavutil/sha.h"
- #include "libavutil/sha512.h"
- #include "libavutil/stereo3d.h"
- #include "libavutil/tea.h"
- #include "libavutil/threadmessage.h"
- #include "libavutil/time.h"
- #include "libavutil/timecode.h"
- #include "libavutil/timestamp.h"
- #include "libavutil/tree.h"
- #include "libavutil/twofish.h"
- #include "libavutil/version.h"
- #include "libavutil/xtea.h"
- #include "libswresample/swresample.h"
- #include "libswresample/version.h"
- #include "libswscale/swscale.h"
- #include "libswscale/version.h"
- #include "CarEyeEncoderAPI.h"
- #include "CarEyeFilter_interface.h"
- #include <android/log.h>
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "ffmpeg4android", __VA_ARGS__)
- JNIEXPORT jint JNICALL Java_com_li_sheldon_ffmpeg4android_FFmpegNative_ffmpeg_1h264
- (JNIEnv* env, jobject obj, jint codecID)
- {
- AVCodec* codec = NULL;
- av_register_all();//该函数在全部基于ffmpeg的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等
- codec = avcodec_find_decoder(codecID);//经过code ID查找一个已经注册的音视频编码器。H264的codecID是28,因此咱们java那边传28下来若是检测到H264注册过了这边codec就不为空,返回0
- if(codec != NULL){
- return 0;
- }else{
- return -1;
- }
- }
- CarEyeFilter gFliter;
- JNIEXPORT jint JNICALL Java_com_li_sheldon_ffmpeg4android_FFmpegNative_OpenOSD(JNIEnv* env, jobject obj, jint width, jint height, jint startX, jint startY, jint FontSize, jint color, jstring filename, jstring content )
- {
- char* Name;
- char* pContent;
- gFliter.para.X = startX;
- gFliter.para.Y = startY;
- gFliter.para.width = width;
- gFliter.para.height = height;
- gFliter.para.FontSize = FontSize;
- gFliter.para.FontColor = color;
- gFliter.para.fps = 25;
- Name=(*env)->GetStringUTFChars(env,filename, JNI_FALSE);
- pContent=(*env)->GetStringUTFChars(env,content, JNI_FALSE);
- strcpy(gFliter.para.FontName, Name);
- strcpy(gFliter.para.SubTitle, pContent);
- gFliter.para.Transparency = 0.7;
- (*env)->ReleaseStringUTFChars(env, filename, Name);
- (*env)->ReleaseStringUTFChars(env, content, pContent);
- return CarEye_OpenOsd(&gFliter, gFliter.para);
- }
- JNIEXPORT jint JNICALL Java_com_li_sheldon_ffmpeg4android_FFmpegNative_CloseOSD(JNIEnv* env, jobject obj)
- {
- return CarEye_CloseOsd(&gFliter);
- }
- JNIEXPORT jint JNICALL Java_com_li_sheldon_ffmpeg4android_FFmpegNative_AddOSD(JNIEnv* env, jobject obj, jbyteArray frame, jstring txtoverlay)
- {
- unsigned char * pBuffer;
- int ret;
- char *txt;
- CarEye_YUVFrame yuv_frame;
- pBuffer = (*env)->GetByteArrayElements(env,frame, 0 );
- int len = (*env)->GetArrayLength(env,frame);
- yuv_frame.Y = pBuffer;
- yuv_frame.YSize = len*2/3;
- yuv_frame.U = &pBuffer[len*2/3];
- yuv_frame.USize = len/6;
- yuv_frame.V = &pBuffer[len*5/6];
- yuv_frame.VSize = len/6;
- txt = (*env)->GetStringUTFChars(env,txtoverlay, JNI_FALSE);
- strcpy(gFliter.para.SubTitle, txt);
- ret = CarEye_add_osd(&gFliter,&yuv_frame,gFliter.para);
- (*env)->ReleaseStringUTFChars(env, txtoverlay, txt);
- (*env)->ReleaseByteArrayElements(env,frame,pBuffer,0);
- return ret;
- }
OK, 写一个简单例子测试下:
[java] view plain copy
- package com.li.sheldon.ffmpeg4android;
- import android.util.Log;
- /**
- * Created by sheldon on 17-1-4.
- */
- public class FFmpegNative {
- static{
- System.loadLibrary("avcodec-57");
- System.loadLibrary("avfilter-6");
- System.loadLibrary("avformat-57");
- System.loadLibrary("avutil-55");
- System.loadLibrary("swresample-2");
- System.loadLibrary("swscale-4");
- System.loadLibrary("ffmpegjni");
- }
- private native int AddOSD(byte[] buffer, String txt);
- private native int CloseOSD();
- private native int OpenOSD(int width, int height, int startX, int startY, int fontsize, int color, String filename, String content);
- private native int ffmpeg_h264(int id);
- public int test_h246(int id){
- return ffmpeg_h264(id);
- }
- public int InitOSD(int width, int height, int startX, int startY, int fontsize, int color, String filename, String content)
- {
- return OpenOSD(width,height,startX,startY,fontsize,color, filename,content );
- }
- public int DelOSD()
- {
- return CloseOSD();
- }
- public int blendOSD(byte[] buffer,String txt)
- {
- return AddOSD(buffer,txt);
- }
- }
[cpp] view plain copy
- package com.li.sheldon.ffmpeg4android;
- import android.app.Activity;
- import android.os.Bundle;
- import android.util.Log;
- import android.widget.TextView;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- public class MainActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- int codec_id = 28;
- final FFmpegNative ffmpeg = new FFmpegNative();
- int tmp = ffmpeg.test_h246(codec_id); //28 is the H264 Codec ID
- TextView tv = (TextView) this.findViewById(R.id.hello_ffmpeg);
- tv.setText(tmp == 0 ? "Support Codec ID:" + codec_id : "Not support Codec ID:" + codec_id);
- Log.d("ffmpeg4android", "OSD init start: ");
- new Thread(new Runnable() {
- @Override
- public void run() {
- int loop = 0;
- FileOutputStream out;
- FileInputStream in;
- FFmpegNative ffmpeg = new FFmpegNative();
- int ret = ffmpeg.InitOSD(1280, 720, 10, 10, 28, 0x00ff00, String.format("/mnt/sdcard/arial.ttf"), String.format("ddddd"));
- if (ret != 0) {
- Log.d("ffmpeg4android", "OSD init fail: "+ret);
- }
- Log.d("ffmpeg4android", "OSD blend ");
- byte[] data=new byte[1280*720*3/2];
- try {
- File f = new File("/mnt/sdcard/out.yuv");
- if(f.exists()) f.delete();
- f.createNewFile();
- out = new FileOutputStream(f);
- File input = new File("/mnt/sdcard/input.yuv");
- in = new FileInputStream(input);
- int len;
- while(loop<1000)
- {
- if(in.read(data,0,1280*720*3/2)<0)
- {
- Log.d("ffmpeg4android", "read fail:");
- break;
- }else {
- String txt = "car-eye-filter" + new SimpleDateFormat("yyyy-MM-dd").format(new Date())+loop;
- int result = ffmpeg.blendOSD(data, txt);
- out.write(data,0,1280*720*3/2);
- Log.d("ffmpeg4android", "write data sucessful:"+result+"data[0]"+data[0]);
- }
- loop++;
- }
- in.close();
- out.close();
- ffmpeg.DelOSD();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
- }
生成的YUV数据用播放器打开以下
car-eye开源官方网址:www.car-eye.cn
car-eye 流媒体平台网址:www.liveoss.com
car-eye 技术官方邮箱: support@car-eye.cn
car-eye技术交流QQ群: 590411159
CopyRight© car-eye 开源团队 2018