首先来看一副图,用来纪念对视频领域做出贡献的雷神:
当然在这个图片里面的decode不是必须的,Filter 模块本身是一个非常独立的模块,但因为相关的程序,给人造成了他必须要依赖于decoder或者encoder来工作。
OK 不多说,先看看内部实现代码:
[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 copy
#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
实现部分代码:
[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;
[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这个库。
#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