parent
d1ea481265
commit
8548be13ac
@ -0,0 +1,325 @@ |
||||
//
|
||||
// Created by frank on 2018/6/4.
|
||||
//
|
||||
#include "libavcodec/avcodec.h" |
||||
#include "libavformat/avformat.h" |
||||
#include "libswscale/swscale.h" |
||||
#include <android/native_window.h> |
||||
#include <android/native_window_jni.h> |
||||
#include <stdio.h> |
||||
#include <unistd.h> |
||||
#include <libavutil/imgutils.h> |
||||
#include <android/log.h> |
||||
#include <libavfilter/buffersink.h> |
||||
#include <libavfilter/buffersrc.h> |
||||
#include <libavfilter/avfiltergraph.h> |
||||
#include <libavutil/opt.h> |
||||
|
||||
#define TAG "VideoFilter" |
||||
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO, TAG, FORMAT, ##__VA_ARGS__); |
||||
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, TAG, FORMAT, ##__VA_ARGS__); |
||||
|
||||
AVFilterContext *buffersink_ctx; |
||||
|
||||
AVFilterContext *buffersrc_ctx; |
||||
|
||||
AVFilterGraph *filter_graph; |
||||
|
||||
int video_stream_index = -1; |
||||
|
||||
AVFormatContext *pFormatCtx; |
||||
|
||||
AVCodecContext *pCodecCtx; |
||||
|
||||
ANativeWindow_Buffer windowBuffer; |
||||
|
||||
ANativeWindow* nativeWindow; |
||||
|
||||
AVFrame * pFrame; |
||||
|
||||
AVFrame * pFrameRGBA; |
||||
|
||||
uint8_t * buffer; |
||||
|
||||
struct SwsContext *sws_ctx; |
||||
|
||||
/**
|
||||
* colorbalance: |
||||
rs/gs/bs |
||||
Adjust red, green and blue shadows (darkest pixels). |
||||
rm/gm/bm |
||||
Adjust red, green and blue midtones (medium pixels). |
||||
rh/gh/bh |
||||
Adjust red, green and blue highlights (brightest pixels). |
||||
*/ |
||||
|
||||
/**
|
||||
* Draw text |
||||
* Draw a text string or text from a specified file on top of a video, using the libfreetype library. |
||||
* To enable compilation of this filter, you need to configure FFmpeg with --enable-libfreetype. |
||||
* To enable default font fallback and the font option you need to configure FFmpeg with --enable-libfontconfig. |
||||
* To enable the text_shaping option, you need to configure FFmpeg with --enable-libfribidi. |
||||
* Video center: drawtext="fontsize=30:fontfile=FreeSerif.ttf:text='hello world':x=(w-text_w)/2:y=(h-text_h)/2" |
||||
*/ |
||||
|
||||
//const char *filter_descr = "lutyuv='u=128:v=128'";//黑白
|
||||
//const char *filter_descr = "hue='h=60:s=-3'";//hue滤镜-->Set the hue to 60 degrees and the saturation to -3
|
||||
//const char *filter_descr = "vflip";//上下反序
|
||||
//const char *filter_descr = "hflip";//左右反序
|
||||
//const char *filter_descr = "rotate=90";//旋转90°
|
||||
//const char *filter_descr = "colorbalance=bs=0.3";//添加蓝色背景
|
||||
//const char *filter_descr = "drawbox=x=100:y=100:w=100:h=100:color=pink@0.5'";//绘制矩形
|
||||
//const char *filter_descr = "drawgrid=w=iw/3:h=ih/3:t=2:c=white@0.5";//九宫格分割
|
||||
//const char *filter_descr = "edgedetect=low=0.1:high=0.4";//边缘检测
|
||||
//const char *filter_descr = "fftfilt=dc_Y=0:weight_Y='exp(-4 * ((Y+X)/(W+H)))'";//fft模糊
|
||||
//const char *filter_descr = "lutrgb='r=0:g=0'";//去掉红色、绿色分量,只保留蓝色
|
||||
//const char *filter_descr = "noise=alls=20:allf=t+u";//添加噪声
|
||||
//const char *filter_descr = "pad=640:480:40:0:violet";//pad填充颜色
|
||||
//const char *filter_descr = "vignette='PI/4+random(1)*PI/50':eval=frame";//闪烁装饰-->Make a flickering vignetting
|
||||
|
||||
//const char *filter_descr = "gblur=sigma=0.5:steps=1:planes=1:sigmaV=1";//高斯模糊
|
||||
//const char *filter_descr = "drawtext=fontfile='app/src/main/cpp/arial.ttf':fontcolor=green:fontsize=30:text='Hello world'";//绘制文字
|
||||
//const char *filter_descr = "movie=my_logo.png[wm];[in][wm]overlay=5:5[out]";//添加图片水印
|
||||
|
||||
//初始化滤波器
|
||||
int init_filters(const char *filters_descr) { |
||||
char args[512]; |
||||
int ret = 0; |
||||
AVFilter *buffersrc = avfilter_get_by_name("buffer"); |
||||
AVFilter *buffersink = avfilter_get_by_name("buffersink"); |
||||
AVFilterInOut *outputs = avfilter_inout_alloc(); |
||||
AVFilterInOut *inputs = avfilter_inout_alloc(); |
||||
AVRational time_base = pFormatCtx->streams[video_stream_index]->time_base; |
||||
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE }; |
||||
|
||||
filter_graph = avfilter_graph_alloc(); |
||||
if (!outputs || !inputs || !filter_graph) { |
||||
ret = AVERROR(ENOMEM); |
||||
goto end; |
||||
} |
||||
|
||||
/* buffer video source: the decoded frames from the decoder will be inserted here. */ |
||||
snprintf(args, sizeof(args), |
||||
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", |
||||
pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, |
||||
time_base.num, time_base.den, |
||||
pCodecCtx->sample_aspect_ratio.num, pCodecCtx->sample_aspect_ratio.den); |
||||
|
||||
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", |
||||
args, NULL, filter_graph); |
||||
if (ret < 0) { |
||||
LOGE("Cannot create buffer source\n"); |
||||
goto end; |
||||
} |
||||
|
||||
/* buffer video sink: to terminate the filter chain. */ |
||||
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", |
||||
NULL, NULL, filter_graph); |
||||
if (ret < 0) { |
||||
LOGE("Cannot create buffer sink\n"); |
||||
goto end; |
||||
} |
||||
|
||||
ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts, |
||||
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); |
||||
if (ret < 0) { |
||||
LOGE("Cannot set output pixel format\n"); |
||||
goto end; |
||||
} |
||||
|
||||
/*
|
||||
* The buffer source output must be connected to the input pad of |
||||
* the first filter described by filters_descr; since the first |
||||
* filter input label is not specified, it is set to "in" by |
||||
* default. |
||||
*/ |
||||
outputs->name = av_strdup("in"); |
||||
outputs->filter_ctx = buffersrc_ctx; |
||||
outputs->pad_idx = 0; |
||||
outputs->next = NULL; |
||||
|
||||
/*
|
||||
* The buffer sink input must be connected to the output pad of |
||||
* the last filter described by filters_descr; since the last |
||||
* filter output label is not specified, it is set to "out" by |
||||
* default. |
||||
*/ |
||||
inputs->name = av_strdup("out"); |
||||
inputs->filter_ctx = buffersink_ctx; |
||||
inputs->pad_idx = 0; |
||||
inputs->next = NULL; |
||||
|
||||
if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr, |
||||
&inputs, &outputs, NULL)) < 0) |
||||
goto end; |
||||
|
||||
if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) |
||||
goto end; |
||||
|
||||
end: |
||||
avfilter_inout_free(&inputs); |
||||
avfilter_inout_free(&outputs); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
int open_input(JNIEnv * env, const char* file_name, jobject surface){ |
||||
LOGI("open file:%s\n", file_name); |
||||
//注册所有组件
|
||||
av_register_all(); |
||||
//注册滤波器
|
||||
avfilter_register_all(); |
||||
//分配上下文
|
||||
pFormatCtx = avformat_alloc_context(); |
||||
//打开视频文件
|
||||
if(avformat_open_input(&pFormatCtx, file_name, NULL, NULL)!=0) { |
||||
LOGE("Couldn't open file:%s\n", file_name); |
||||
return -1; |
||||
} |
||||
//检索多媒体流信息
|
||||
if(avformat_find_stream_info(pFormatCtx, NULL)<0) { |
||||
LOGE("Couldn't find stream information."); |
||||
return -1; |
||||
} |
||||
//寻找视频流的第一帧
|
||||
int i; |
||||
for (i = 0; i < pFormatCtx->nb_streams; i++) { |
||||
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO |
||||
&& video_stream_index < 0) { |
||||
video_stream_index = i; |
||||
} |
||||
} |
||||
if(video_stream_index == -1) { |
||||
LOGE("couldn't find a video stream."); |
||||
return -1; |
||||
} |
||||
|
||||
//获取codec上下文指针
|
||||
pCodecCtx = pFormatCtx->streams[video_stream_index]->codec; |
||||
//寻找视频流的解码器
|
||||
AVCodec * pCodec = avcodec_find_decoder(pCodecCtx->codec_id); |
||||
if(pCodec==NULL) { |
||||
LOGE("couldn't find Codec."); |
||||
return -1; |
||||
} |
||||
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { |
||||
LOGE("Couldn't open codec."); |
||||
return -1; |
||||
} |
||||
// 获取native window
|
||||
nativeWindow = ANativeWindow_fromSurface(env, surface); |
||||
|
||||
// 设置native window的buffer大小,可自动拉伸
|
||||
ANativeWindow_setBuffersGeometry(nativeWindow, pCodecCtx->width, pCodecCtx->height, WINDOW_FORMAT_RGBA_8888); |
||||
//申请内存
|
||||
pFrame = av_frame_alloc(); |
||||
pFrameRGBA = av_frame_alloc(); |
||||
if(pFrameRGBA == NULL || pFrame == NULL) { |
||||
LOGE("Couldn't allocate video frame."); |
||||
return -1; |
||||
} |
||||
// buffer中数据用于渲染,且格式为RGBA
|
||||
int numBytes=av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height, 1); |
||||
|
||||
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); |
||||
av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA, |
||||
pCodecCtx->width, pCodecCtx->height, 1); |
||||
// 由于解码出来的帧格式不是RGBA的,在渲染之前需要进行格式转换
|
||||
sws_ctx = sws_getContext(pCodecCtx->width, |
||||
pCodecCtx->height, |
||||
pCodecCtx->pix_fmt, |
||||
pCodecCtx->width, |
||||
pCodecCtx->height, |
||||
AV_PIX_FMT_RGBA, |
||||
SWS_BILINEAR, |
||||
NULL, |
||||
NULL, |
||||
NULL); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_VideoPlayer_filter |
||||
(JNIEnv * env, jclass clazz, jstring filePath, jobject surface, jstring filterDescr){ |
||||
|
||||
int ret; |
||||
const char * file_name = (*env)->GetStringUTFChars(env, filePath, JNI_FALSE); |
||||
const char *filter_descr = (*env)->GetStringUTFChars(env, filterDescr, JNI_FALSE); |
||||
//打开输入文件
|
||||
if((ret = open_input(env, file_name, surface) < 0)){ |
||||
LOGE("Couldn't allocate video frame."); |
||||
goto end; |
||||
} |
||||
//注册滤波器
|
||||
avfilter_register_all(); |
||||
|
||||
AVFrame * filter_frame = av_frame_alloc(); |
||||
if(filter_frame == NULL) { |
||||
LOGE("Couldn't allocate video frame."); |
||||
ret = -1; |
||||
goto end; |
||||
} |
||||
|
||||
//初始化滤波器
|
||||
if ((ret = init_filters(filter_descr)) < 0){ |
||||
LOGE("init_filter error, ret=%d\n", ret); |
||||
goto end; |
||||
} |
||||
|
||||
int frameFinished; |
||||
AVPacket packet; |
||||
|
||||
while(av_read_frame(pFormatCtx, &packet)>=0) { |
||||
//判断是否为视频流
|
||||
if(packet.stream_index == video_stream_index) { |
||||
//对该帧进行解码
|
||||
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); |
||||
|
||||
if (frameFinished) { |
||||
//把解码后视频帧添加到filter_graph
|
||||
if (av_buffersrc_add_frame_flags(buffersrc_ctx, pFrame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) { |
||||
LOGE("Error while feeding the filter_graph\n"); |
||||
break; |
||||
} |
||||
//把滤波后的视频帧从filter graph取出来
|
||||
ret = av_buffersink_get_frame(buffersink_ctx, filter_frame); |
||||
if (ret >= 0){ |
||||
// lock native window
|
||||
ANativeWindow_lock(nativeWindow, &windowBuffer, 0); |
||||
// 格式转换
|
||||
sws_scale(sws_ctx, (uint8_t const * const *)filter_frame->data, |
||||
filter_frame->linesize, 0, pCodecCtx->height, |
||||
pFrameRGBA->data, pFrameRGBA->linesize); |
||||
// 获取stride
|
||||
uint8_t * dst = windowBuffer.bits; |
||||
int dstStride = windowBuffer.stride * 4; |
||||
uint8_t * src = pFrameRGBA->data[0]; |
||||
int srcStride = pFrameRGBA->linesize[0]; |
||||
// 由于window的stride和帧的stride不同,因此需要逐行复制
|
||||
int h; |
||||
for (h = 0; h < pCodecCtx->height; h++) { |
||||
memcpy(dst + h * dstStride, src + h * srcStride, (size_t) srcStride); |
||||
} |
||||
ANativeWindow_unlockAndPost(nativeWindow); |
||||
} |
||||
av_frame_unref(filter_frame); |
||||
} |
||||
av_frame_unref(pFrame); |
||||
//延迟等待
|
||||
usleep((unsigned long) (1000 * 40)); |
||||
} |
||||
av_packet_unref(&packet); |
||||
} |
||||
end: |
||||
//释放内存以及关闭文件
|
||||
av_free(buffer); |
||||
av_free(pFrameRGBA); |
||||
av_free(filter_frame); |
||||
av_free(pFrame); |
||||
avcodec_close(pCodecCtx); |
||||
avformat_close_input(&pFormatCtx); |
||||
avfilter_graph_free(&filter_graph); |
||||
(*env)->ReleaseStringUTFChars(env, filePath, file_name); |
||||
(*env)->ReleaseStringUTFChars(env, filterDescr, filter_descr); |
||||
return ret; |
||||
} |
@ -0,0 +1,128 @@ |
||||
package com.frank.ffmpeg.activity; |
||||
|
||||
import android.os.Environment; |
||||
import android.support.v7.app.AppCompatActivity; |
||||
import android.os.Bundle; |
||||
import android.view.SurfaceHolder; |
||||
import android.view.SurfaceView; |
||||
import android.view.View; |
||||
import com.frank.ffmpeg.R; |
||||
import com.frank.ffmpeg.VideoPlayer; |
||||
import java.io.File; |
||||
|
||||
public class FilterActivity extends AppCompatActivity implements View.OnClickListener, SurfaceHolder.Callback{ |
||||
|
||||
//SD卡根目录
|
||||
private final static String PATH = Environment.getExternalStorageDirectory().getPath(); |
||||
//本地视频路径
|
||||
private final static String VIDEO_PATH = PATH + File.separator + "Beyond.mp4"; |
||||
|
||||
private VideoPlayer videoPlayer; |
||||
private SurfaceHolder surfaceHolder; |
||||
private boolean surfaceCreated; |
||||
|
||||
private String[] filters = new String[]{ |
||||
"lutyuv='u=128:v=128'", |
||||
"hue='h=60:s=-3'", |
||||
"lutrgb='r=0:g=0'", |
||||
"edgedetect=low=0.1:high=0.4", |
||||
"fftfilt=dc_Y=0:weight_Y='exp(-4 * ((Y+X)/(W+H)))'", |
||||
"drawgrid=w=iw/3:h=ih/3:t=2:c=white@0.5", |
||||
"colorbalance=bs=0.3", |
||||
"drawbox=x=100:y=100:w=100:h=100:color=pink@0.5'", |
||||
"rotate=90", |
||||
"vflip", |
||||
"noise=alls=20:allf=t+u", |
||||
"vignette='PI/4+random(1)*PI/50':eval=frame" |
||||
}; |
||||
private int filterType; |
||||
|
||||
@Override |
||||
protected void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
setContentView(R.layout.activity_filter); |
||||
|
||||
initView(); |
||||
} |
||||
|
||||
private void initView(){ |
||||
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface_filter); |
||||
surfaceHolder = surfaceView.getHolder(); |
||||
surfaceHolder.addCallback(this); |
||||
videoPlayer = new VideoPlayer(); |
||||
|
||||
findViewById(R.id.btn_sketch).setOnClickListener(this); |
||||
findViewById(R.id.btn_hue).setOnClickListener(this); |
||||
findViewById(R.id.btn_lut).setOnClickListener(this); |
||||
findViewById(R.id.btn_edge).setOnClickListener(this); |
||||
findViewById(R.id.btn_blur).setOnClickListener(this); |
||||
findViewById(R.id.btn_grid).setOnClickListener(this); |
||||
findViewById(R.id.btn_balance).setOnClickListener(this); |
||||
findViewById(R.id.btn_box).setOnClickListener(this); |
||||
findViewById(R.id.btn_rotate).setOnClickListener(this); |
||||
findViewById(R.id.btn_flip).setOnClickListener(this); |
||||
} |
||||
|
||||
@Override |
||||
public void onClick(View v) { |
||||
if(!surfaceCreated) |
||||
return; |
||||
switch (v.getId()){ |
||||
case R.id.btn_sketch://素描
|
||||
filterType = 0; |
||||
break; |
||||
case R.id.btn_hue://hue
|
||||
filterType = 1; |
||||
break; |
||||
case R.id.btn_lut://lut
|
||||
filterType = 2; |
||||
break; |
||||
case R.id.btn_edge://边缘
|
||||
filterType = 3; |
||||
break; |
||||
case R.id.btn_blur://模糊
|
||||
filterType = 4; |
||||
break; |
||||
case R.id.btn_grid://九宫格
|
||||
filterType = 5; |
||||
break; |
||||
case R.id.btn_balance://色彩平衡
|
||||
filterType = 6; |
||||
break; |
||||
case R.id.btn_box://box绘制矩形区域
|
||||
filterType = 7; |
||||
break; |
||||
case R.id.btn_rotate://旋转
|
||||
filterType = 8; |
||||
break; |
||||
case R.id.btn_flip://翻转
|
||||
filterType = 9; |
||||
break; |
||||
default: |
||||
filterType = 0; |
||||
break; |
||||
} |
||||
new Thread(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
videoPlayer.filter(VIDEO_PATH, surfaceHolder.getSurface(), filters[filterType]); |
||||
} |
||||
}).start(); |
||||
} |
||||
|
||||
@Override |
||||
public void surfaceCreated(SurfaceHolder holder) { |
||||
surfaceCreated = true; |
||||
} |
||||
|
||||
@Override |
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void surfaceDestroyed(SurfaceHolder holder) { |
||||
surfaceCreated = false; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,69 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
|
||||
<SurfaceView |
||||
android:id="@+id/surface_filter" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" /> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="horizontal" |
||||
android:scrollbarAlwaysDrawHorizontalTrack="true" |
||||
android:layout_alignParentBottom="true"> |
||||
<Button |
||||
android:id="@+id/btn_sketch" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_sketch"/> |
||||
<Button |
||||
android:id="@+id/btn_hue" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_hue"/> |
||||
<Button |
||||
android:id="@+id/btn_lut" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_lut"/> |
||||
<Button |
||||
android:id="@+id/btn_edge" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_edge"/> |
||||
<Button |
||||
android:id="@+id/btn_blur" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_blur"/> |
||||
<Button |
||||
android:id="@+id/btn_grid" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_grid"/> |
||||
<Button |
||||
android:id="@+id/btn_balance" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_balance"/> |
||||
<Button |
||||
android:id="@+id/btn_box" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_box"/> |
||||
<Button |
||||
android:id="@+id/btn_rotate" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_rotate"/> |
||||
<Button |
||||
android:id="@+id/btn_flip" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="@string/video_flip"/> |
||||
</LinearLayout> |
||||
|
||||
</RelativeLayout> |
Loading…
Reference in new issue