向mp4视频添加视觉滤镜

6
我正在尝试为Android中的视频添加视觉滤镜。它应该看起来像Instagram一样,录制视频后,您可以从列表中选择一个视觉滤镜,然后应用它。到目前为止,我找到的最好的选择是GPUImage,它有多个滤镜选项,但仅适用于图像。
录制视频后,我会在temp文件夹中创建一个.mp4文件,并在上传之前打开与下面图片类似的屏幕。我需要创建类似的滤镜选项和滤镜添加。
是否有可用的API可以帮助我,或者是否有人有源代码?

尝试这个 https://dev59.com/nHjZa4cB1Zd3GeqPeHKQ#20714782 - Tara
4个回答

1
花了我一些时间,但我用 FFmpeg 解决了问题。由于我的项目已经在使用 bravobit FFmpeg (Bravobit ffmpeg),所以我决定继续使用它。我添加了所有代码,以防有人遇到相同的问题。

首先,我必须创建一个 Horizontal scrollview

<HorizontalScrollView
    android:layout_width="wrap_content"
    android:layout_height="150dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentStart="true"
    android:layout_gravity="center_vertical"
    android:layout_marginBottom="143dp"
    android:scrollbars="none">

    <LinearLayout
        android:id="@+id/filter_list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:orientation="horizontal"></LinearLayout>
</HorizontalScrollView>

并创建一个filter_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="95dp"
    android:layout_height="wrap_content"

    android:layout_marginLeft="4dp"
    android:layout_marginStart="4dp"
    android:orientation="vertical">

    <TextView
        android:id="@+id/filter_item_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="5dp"
        android:layout_marginTop="5dp"
        android:textColor="#ff0000"
        android:textStyle="bold"
        android:textSize="14dp" />


    <android.support.v7.widget.AppCompatImageView
        android:id="@+id/filter_item_image"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:layout_below="@+id/filter_item_name"
        android:scaleType="centerCrop" />


</RelativeLayout>

接下来,我从我的视频文件中获取缩略图,并使用该缩略图调用一个方法:

shareToFragment.setThumbNailImage(getVideoThumbnail(cameraOutputFile.getPath()));

public static Bitmap getVideoThumbnail(String path) {
    Bitmap bitmap = null;

    FFmpegMediaMetadataRetriever fmmr = new FFmpegMediaMetadataRetriever();

    try {
        fmmr.setDataSource(path);

        final byte[] data = fmmr.getEmbeddedPicture();

        if (data != null) {
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
        }

        if (bitmap == null) {
            bitmap = fmmr.getFrameAtTime();
        }
    } catch (Exception e) {
        bitmap = null;
    } finally {
        fmmr.release();
    }
    return bitmap;
}

现在,最终的创建和使用过滤器的代码如下:

String[] filters = new String[] {"colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131",  "curves=vintage", "curves=negative", "hue=s=0"};
String[] filterNames = new String[] {"Sepia",  "Vintage", "Negative", "Black/White"};
int loopCounter;

public void setThumbNailImage(Bitmap image) {
    loopCounter = -1;
    mGallery.setVisibility(View.VISIBLE);
    LayoutInflater mInflater = LayoutInflater.from(getActivity());

    ffmpeg = FFmpeg.getInstance(context);

    createNewFileForImageAndVideoNoFilter();
    bitmapToFile(image);

    addFilter(mInflater);

}

private void addFilter(LayoutInflater mInflater){
    loopCounter++;
    View view = mInflater.inflate(R.layout.filter_item,
            mGallery, false);

    createNewFileForFilteredImage();



    String[] cmd = { "-i",  imageToBeFiltered.toString(), "-filter_complex", filters[loopCounter], imageWithFilter.toString()};

    ffmpeg.execute(cmd, new ExecuteBinaryResponseHandler() {
        @Override
        public void onSuccess(String message) {
            super.onSuccess(message);

            Bitmap image = BitmapFactory.decodeFile(imageWithFilter.getAbsolutePath());
            ImageView img = (ImageView) view.findViewById(R.id.filter_item_image);
            img.setImageBitmap(image);
            mGallery.addView(view);
            TextView txt = (TextView) view.findViewById(R.id.filter_item_name);
            txt.setText(filterNames[loopCounter]);
            view.setId(loopCounter);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    filteredVideo.delete();
                    String[] cmd = { "-i",  originalVideoFile.toString(), "-filter_complex", filters[view.getId()], "-pix_fmt", "yuv420p",  filteredVideo.toString()};
                    ffmpeg.execute(cmd, new ExecuteBinaryResponseHandler() {
                        @Override
                        public void onSuccess(String message) {
                            super.onSuccess(message);
                            System.out.println("ffmpegVideo: succ" + message);
                            Toast.makeText(context, "Done with adding flter", Toast.LENGTH_LONG).show();
                            somethingYouWannaDoWithTheOutputFile();
                        }
                        @Override
                        public void onFailure(String message) {
                            super.onFailure(message);
                            System.out.println("ffmpegVideo: faill" + message);
                        }

                        @Override
                        public void onProgress(String message) {
                            super.onProgress(message);
                            Toast.makeText(context, "Adding filter", Toast.LENGTH_LONG).show();
                        }
                    });

                }
            });

            if (loopCounter+1 < filters.length) addFilter(mInflater);
        }
        @Override
        public void onFailure(String message) {
            super.onFailure(message);
            if (loopCounter+1 < filters.length) addFilter(mInflater);
        }
    });
}


public void createNewFileForImageAndVideoNoFilter(){
    filteredVideo = new File(context.getFilesDir()+ Common.TEMP_LOCAL_DIR + "/" + "filteredVideo.mp4");
    imageToBeFiltered = new File(context.getFilesDir()+ Common.TEMP_LOCAL_DIR + "/" + "_imageToBeFiltered.png");
    if(filteredVideo.exists()){
        filteredVideo.delete();
        imageToBeFiltered.delete();
    }
}

private void bitmapToFile(Bitmap bitmap){
    try {
        OutputStream os = new BufferedOutputStream(new FileOutputStream(imageToBeFiltered));
        bitmap.compress(Bitmap.CompressFormat.PNG, 0, os);
        os.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

private void createNewFileForFilteredImage(){
    imageWithFilter = new File(context.getFilesDir()+ Common.TEMP_LOCAL_DIR + "/" + "_filteredImage.png");
    if(imageWithFilter.exists()){
        imageWithFilter.delete();
    }
}

起初我因为没有添加"-pix_fmt","yuv420p"而使.mp4文件被损坏。你可以在这里阅读更多信息: FFmpeg视频过滤器破坏mp4文件

1
你需要重新编码mp4文件以便将过滤器应用到每一帧。我能想到两种方法,但都需要高级编程技能。我认为最简单的方法是使用FFMPEG(如果要重新编码,请确保检查许可证)。这个链接 可能会帮助您为Android编译它。完成后,请查看FFMPEG文档和论坛以获取过滤器和叠加的信息。另一个(免费)方法是使用MediaCodec重新编码视频,并使用GL着色器来操作您的帧。Grafika 是一个可以为此提供必要工具的项目。此外,可能在互联网上有两种方法的预构建库,请确保先使用给定信息进行研究。

我已经在使用FFMPEG将pcm转换为WAV,并且我已经看到了一些允许一些基本过滤器的方法,但更难的部分是您可以滚动的列表(如图所示),选择过滤器,然后应用它。 - Richard
你应该能够从视频中获取任何帧,对图像应用滤镜并在用户界面中显示。不幸的是,我不能给你任何代码示例,但是FFMPEG可以实现这一功能。 - Endre Börcsök
我会调查这个。 - Richard
我能否直接找到FFmpeg视觉滤镜的链接?我能找到的只有裁剪和编辑滤镜。 - Richard
尝试在互联网上搜索常见的效果类型。这里有一个棕褐色的链接:https://dev59.com/R4zda4cB1Zd3GeqPjjcr。但你可能想使用“鱼眼”或其他东西。 - Endre Börcsök

1

你尝试过这个吗?它使用FFMPEG添加滤镜/裁剪和更多编辑功能,这可以作为一个库帮助你并提供一些想法,它有一个使用此库构建的演示应用程序,可以在Play商店这里获得。


你有没有任何示例可以展示如何将输出的mp4文件保存在“temp”文件夹中,并添加过滤器呢?因为我并不是很理解他们的文档。 - Richard

1

除了FFMPEG的方法外,我发现对自己有用的是使用GLSurfaceView。思路是在GLSurfaceView上渲染视频,并使用openGL渲染滤镜。请查看this project


能否将输出文件保存到外部存储器,并添加过滤器? - Richard
好的,请查看 Grafika 项目,特别是这个 - Udit Mukherjee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接