首先來看一副圖,用來記念對視頻領域作出貢獻的雷神: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