添加视频滤镜特效

滤镜特效:素描、色彩平衡、模糊、hue、lut、翻转、旋转、九宫格、边缘检测等
pull/107/head
frank 6 years ago
parent d1ea481265
commit 8548be13ac
  1. 3
      app/CMakeLists.txt
  2. 10
      app/src/main/AndroidManifest.xml
  3. 325
      app/src/main/cpp/video_filter.c
  4. 2
      app/src/main/java/com/frank/ffmpeg/VideoPlayer.java
  5. 128
      app/src/main/java/com/frank/ffmpeg/activity/FilterActivity.java
  6. 20
      app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
  7. 69
      app/src/main/res/layout/activity_filter.xml
  8. 11
      app/src/main/res/layout/activity_main.xml
  9. 13
      app/src/main/res/values/strings.xml

@ -28,7 +28,8 @@ add_library( # Sets the name of the library.
src/main/cpp/video_player.c
src/main/cpp/ffmpeg_pusher.cpp
src/main/cpp/AVpacket_queue.c
src/main/cpp/media_player.c)
src/main/cpp/media_player.c
src/main/cpp/video_filter.c)
add_library( ffmpeg
SHARED

@ -38,12 +38,18 @@
android:name=".activity.VideoPlayerActivity"
android:screenOrientation="landscape" />
<!-- 音视频解码播放 -->
<activity android:name=".activity.MediaPlayerActivity"
<activity
android:name=".activity.MediaPlayerActivity"
android:screenOrientation="landscape" />
<!-- 本地推流直播 -->
<activity android:name=".activity.PushActivity" />
<!-- 实时推流直播 -->
<activity android:name=".activity.LiveActivity"
<activity
android:name=".activity.LiveActivity"
android:screenOrientation="landscape" />
<!--滤镜特效-->
<activity
android:name=".activity.FilterActivity"
android:screenOrientation="landscape" />
</application>

@ -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;
}

@ -13,4 +13,6 @@ public class VideoPlayer {
public native int play(String filePath, Object surface);
public native int setPlayRate(float playRate);
public native int filter(String filePath, Object surface, String filterType);
}

@ -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;
}
}

@ -4,8 +4,6 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
import com.frank.ffmpeg.R;
/**
@ -29,32 +27,38 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
findViewById(R.id.btn_play).setOnClickListener(this);
findViewById(R.id.btn_push).setOnClickListener(this);
findViewById(R.id.btn_live).setOnClickListener(this);
findViewById(R.id.btn_filter).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent();
switch (v.getId()){
case R.id.btn_audio://音频处理
startActivity(new Intent(MainActivity.this, AudioHandleActivity.class));
intent.setClass(MainActivity.this, AudioHandleActivity.class);
break;
case R.id.btn_video://视频处理
startActivity(new Intent(MainActivity.this, VideoHandleActivity.class));
intent.setClass(MainActivity.this, VideoHandleActivity.class);
break;
case R.id.btn_media://音视频处理
startActivity(new Intent(MainActivity.this, MediaHandleActivity.class));
intent.setClass(MainActivity.this, MediaHandleActivity.class);
break;
case R.id.btn_play://音视频播放
startActivity(new Intent(MainActivity.this, MediaPlayerActivity.class));
intent.setClass(MainActivity.this, MediaPlayerActivity.class);
break;
case R.id.btn_push://FFmpeg推流
startActivity(new Intent(MainActivity.this, PushActivity.class));
intent.setClass(MainActivity.this, PushActivity.class);
break;
case R.id.btn_live://实时推流直播:AAC音频编码、H264视频编码、RTMP推流
startActivity(new Intent(MainActivity.this, LiveActivity.class));
intent.setClass(MainActivity.this, LiveActivity.class);
break;
case R.id.btn_filter://滤镜特效
intent.setClass(MainActivity.this, FilterActivity.class);
break;
default:
break;
}
startActivity(intent);
}
}

@ -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>

@ -7,7 +7,7 @@
android:id="@+id/btn_audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="120dp"
android:layout_marginTop="80dp"
android:layout_centerHorizontal="true"
android:text="@string/audio_handle"/>
@ -56,4 +56,13 @@
android:layout_centerHorizontal="true"
android:layout_below="@+id/btn_push"/>
<Button
android:id="@+id/btn_filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video_filter"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:layout_below="@+id/btn_live"/>
</RelativeLayout>

@ -31,6 +31,7 @@
<string name="video_extract_frame">视频抽帧</string>
<string name="video_part_zoom">视频局部放大</string>
<string name="video_play">视频播放</string>
<string name="video_filter">滤镜特效</string>
<string name="video_slow">慢放</string>
<string name="video_fast">快进</string>
@ -40,4 +41,16 @@
<string name="mute">静音</string>
<string name="sound">声音</string>
<string name="video_sketch">素描</string>
<string name="video_hue">hue</string>
<string name="video_lut">lut</string>
<string name="video_edge">边缘</string>
<string name="video_blur">模糊</string>
<string name="video_grid">九宫格</string>
<string name="video_rotate">旋转</string>
<string name="video_flip">翻转</string>
<string name="video_box">矩形</string>
<string name="video_noise">噪声</string>
<string name="video_balance">色彩平衡</string>
</resources>

Loading…
Cancel
Save