214

我已经在 Android 上编译了 FFmpeg (libffmpeg.so)。现在我必须构建一个像 RockPlayer 这样的应用程序,或者使用现有的 Android 多媒体框架来调用 FFmpeg。

  1. 您有在 Android / StageFright 上集成 FFmpeg 的步骤/程序/代码/示例吗?

  2. 你能指导我如何使用这个库进行多媒体播放吗?

  3. 我有一个要求,我已经有音频和视频传输流,我需要将其提供给 FFmpeg 并对其进行解码/渲染。我如何在 Android 上做到这一点,因为 IOMX API 是基于 OMX 的并且不能在此处插入 FFmpeg?

  4. 我也找不到需要用于播放的 FFmpeg API 的文档。

4

10 回答 10

110

以下是我让 ffmpeg 在 Android 上运行所经历的步骤:

  1. 为 Android 构建 ffmpeg 的静态库。这是通过使用Android Build System构建 olvaffe 的 ffmpeg android 端口 ( libffmpeg ) 来实现的。只需将源放在 /external 和away 下。您还需要从 Android 构建中提取 bionic(libc) 和 zlib(libz),因为 ffmpeg 库依赖于它们。make
  2. 使用 Android NDK创建一个包装 ffmpeg 功能的动态库。有很多关于如何使用 NDK 的文档。基本上你需要编写一些 C/C++ 代码来将你需要的功能从 ffmpeg 导出到一个库中,java 可以通过 JNI 与之交互。NDK 允许您轻松链接到您在步骤 1 中生成的静态库,只需在 Android.mk 中添加与此类似的行:LOCAL_STATIC_LIBRARIES := libavcodec libavformat libavutil libc libz

  3. 使用您的 java 源代码中的 ffmpeg-wrapping 动态库。那里有足够的关于 JNI 的文档,你应该没问题。

关于使用 ffmpeg 进行播放,有很多例子(ffmpeg 二进制文件本身就是一个很好的例子),这里有一个基础教程。最好的文档可以在标题中找到。

祝你好运 :)

于 2011-01-27T18:26:35.823 回答
70

出于各种原因,多媒体在不影响效率的情况下完成任务方面过去而且从来都不是一件容易的事。ffmpeg 每天都在努力改进它。它支持不同格式的编解码器和容器。

现在回答如何使用这个库的问题,我想说在这里写它并不是那么简单。但我可以通过以下方式指导您。

1)在源码的ffmpeg目录下,有output_example.capi_example.c。在这里,您可以看到完成编码/解码的代码。您将了解应该调用 ffmpeg 中的哪个 API。这将是你的第一步。

2)海豚播放器是安卓的开源项目。目前它存在错误,但开发人员正在持续工作。在该项目中,您已经准备好整个设置,您可以使用它来继续您的调查。这是来自 code.google.com 的项目链接或在终端中运行命令“ git clone https://code.google.com/p/dolphin-player/ ”。您可以看到两个名为 P 和 P86 的项目。您可以使用其中任何一个。

我想提供的额外提示是,当您构建 ffmpeg 代码时,您需要在 build.sh 中启用您想要使用的格式的复用器/解复用器/编码器/解码器。否则相应的代码将不会包含在库中。我花了很长时间才意识到这一点。所以想和大家分享一下。

一些基础知识: 当我们说视频文件时,例如:avi,它是音频和视频的组合

视频文件 = 视频 + 音频


视频 = 编解码器 + 复用器 + 解复用器

编解码器 = 编码器 + 解码器

=>视频 = 编码器 + 解码器 + 复用器 + 解复用器(Mpeg4 + Mpeg4 + avi +avi - avi 容器示例)


音频 = 编解码器 + 复用器 + 解复用器

编解码器 = 编码器 + 解码器

=>音频 = 编码器 + 解码器 + 复用器 + 解复用器(mp2 + mp2 + avi + avi - avi 容器示例)


编解码器(名称来源于 en*co*der/*dec*oder 的组合)只是格式的一部分,它定义了用于编码/解码帧的算法。AVI 不是编解码器,它是使用 Mpeg4 的视频编解码器和 mp2 的音频编解码器的容器。

Muxer/demuxer 用于将帧与编码/解码时使用的文件组合/分离。

所以如果要使用avi格式,需要开启Video组件+Audio组件。

例如,对于 avi,您需要启用以下功能。mpeg4 编码器、mpeg4 解码器、mp2 编码器、mp2 解码器、avi muxer、avi demuxer。

呸呸呸呸……

以编程方式 build.sh 应包含以下代码:

--enable-muxer=avi --enable-demuxer=avi (Generic for both audio/video. generally Specific to a container)
--enable-encoder=mpeg4 --enable-decoder=mpeg4(For video support)
--enable-encoder=mp2 --enable-decoder=mp2 (For Audio support)

希望我没有在这一切之后让你更加困惑......

谢谢,需要任何帮助,请告诉我。

于 2012-03-27T08:32:49.670 回答
15

经过大量研究,现在这是我发现的最新的 Android 编译库:

https://github.com/bravobit/FFmpeg-Android

于 2018-11-22T11:04:43.790 回答
13

我发现的最容易构建、最容易使用的实现是由 theguardianproject 团队制作的:https ://github.com/guardianproject/android-ffmpeg

于 2012-10-31T11:15:18.217 回答
11

我做了一个小项目来使用 Android NDK 配置和构建 X264 和 FFMPEG。缺少的主要是一个不错的 JNI 接口,可以通过 Java 访问它,但这是比较容易的部分(相对而言)。当我开始让 JNI 接口适合我自己的使用时,我会推动它。

olvaffe 构建系统的优势在于它不需要 Android.mk 文件来构建库,它只使用常规的 makefile 和工具链。这使得当您从 FFMPEG 或 X264 中提取新更改时停止工作的可能性大大降低。

https://github.com/halfninja/android-ffmpeg-x264

于 2011-06-24T11:33:30.493 回答
7

为了制作我的 FFMPEG 应用程序,我使用了这个项目(https://github.com/hiteshsondhi88/ffmpeg-android-java),所以我不需要编译任何东西。我认为这是在我们的 Android 应用程序中使用 FFMPEG 的简单方法。

有关http://hiteshsondhi88.github.io/ffmpeg-android-java/的更多信息

于 2015-05-18T18:02:04.383 回答
3

受到 Android 实现上的许多其他 FFmpeg 的启发(主要是guadianproject),我找到了一个解决方案(也支持 Lame)。

(跛脚和 FFmpeg:https ://github.com/intervigilium/liblame和http://bambuser.com/opensource

调用 FFmpeg:

new Thread(new Runnable() {

    @Override
    public void run() {

        Looper.prepare();

        FfmpegController ffmpeg = null;

        try {
            ffmpeg = new FfmpegController(context);
        } catch (IOException ioe) {
            Log.e(DEBUG_TAG, "Error loading ffmpeg. " + ioe.getMessage());
        }

        ShellDummy shell = new ShellDummy();
        String mp3BitRate = "192";

        try {
            ffmpeg.extractAudio(in, out, audio, mp3BitRate, shell);
        } catch (IOException e) {
            Log.e(DEBUG_TAG, "IOException running ffmpeg" + e.getMessage());
        } catch (InterruptedException e) {
            Log.e(DEBUG_TAG, "InterruptedException running ffmpeg" + e.getMessage());
        }

        Looper.loop();

    }

}).start();

并处理控制台输出:

private class ShellDummy implements ShellCallback {

    @Override
    public void shellOut(String shellLine) {
        if (someCondition) {
            doSomething(shellLine);
        }
        Utils.logger("d", shellLine, DEBUG_TAG);
    }

    @Override
    public void processComplete(int exitValue) {
        if (exitValue == 0) {
            // Audio job OK, do your stuff: 

                            // i.e.             
                            // write id3 tags,
                            // calls the media scanner,
                            // etc.
        }
    }

    @Override
    public void processNotStartedCheck(boolean started) {
        if (!started) {
                            // Audio job error, as above.
        }
    }
}
于 2013-07-02T14:38:03.643 回答
3

奇怪的是这个项目没有被提及:来自 Appunite 的 AndroidFFmpeg

对于像我这样的懒人来说,它有非常详细的分步说明来复制/粘贴到命令行))

于 2014-11-28T15:04:40.680 回答
3

我有同样的问题,我发现这里的大部分答案都过时了。我最终在 FFMPEG 上编写了一个包装器,以便通过一行代码从 Android 访问。

https://github.com/madhavanmalolan/ffmpegandroidlibrary

于 2017-10-27T11:12:49.673 回答
2

First, add the dependency of FFmpeg library

implementation 'com.writingminds:FFmpegAndroid:0.3.2'

Then load in activity

FFmpeg ffmpeg;
    private void trimVideo(ProgressDialog progressDialog) {

    outputAudioMux = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath()
            + "/VidEffectsFilter" + "/" + new SimpleDateFormat("ddMMyyyy_HHmmss").format(new Date())
            + "filter_apply.mp4";

    if (startTrim.equals("")) {
        startTrim = "00:00:00";
    }

    if (endTrim.equals("")) {
        endTrim = timeTrim(player.getDuration());
    }

    String[] cmd = new String[]{"-ss", startTrim + ".00", "-t", endTrim + ".00", "-noaccurate_seek", "-i", videoPath, "-codec", "copy", "-avoid_negative_ts", "1", outputAudioMux};


    execFFmpegBinary1(cmd, progressDialog);
    }



    private void execFFmpegBinary1(final String[] command, ProgressDialog prpg) {

    ProgressDialog progressDialog = prpg;

    try {
        ffmpeg.execute(command, new ExecuteBinaryResponseHandler() {
            @Override
            public void onFailure(String s) {
                progressDialog.dismiss();
                Toast.makeText(PlayerTestActivity.this, "Fail to generate video", Toast.LENGTH_SHORT).show();
                Log.d(TAG, "FAILED with output : " + s);
            }

            @Override
            public void onSuccess(String s) {
                Log.d(TAG, "SUCCESS wgith output : " + s);

//                    pathVideo = outputAudioMux;
                String finalPath = outputAudioMux;
                videoPath = outputAudioMux;
                Toast.makeText(PlayerTestActivity.this, "Storage Path =" + finalPath, Toast.LENGTH_SHORT).show();

                Intent intent = new Intent(PlayerTestActivity.this, ShareVideoActivity.class);
                intent.putExtra("pathGPU", finalPath);
                startActivity(intent);
                finish();
                MediaScannerConnection.scanFile(PlayerTestActivity.this, new String[]{finalPath}, new String[]{"mp4"}, null);

            }

            @Override
            public void onProgress(String s) {
                Log.d(TAG, "Started gcommand : ffmpeg " + command);
                progressDialog.setMessage("Please Wait video triming...");
            }

            @Override
            public void onStart() {
                Log.d(TAG, "Startedf command : ffmpeg " + command);

            }

            @Override
            public void onFinish() {
                Log.d(TAG, "Finished f command : ffmpeg " + command);
                progressDialog.dismiss();
            }
        });
    } catch (FFmpegCommandAlreadyRunningException e) {
        // do nothing for now
    }
}

  private void loadFFMpegBinary() {
    try {
        if (ffmpeg == null) {
            ffmpeg = FFmpeg.getInstance(this);
        }
        ffmpeg.loadBinary(new LoadBinaryResponseHandler() {
            @Override
            public void onFailure() {
                showUnsupportedExceptionDialog();
            }

            @Override
            public void onSuccess() {
                Log.d("dd", "ffmpeg : correct Loaded");
            }
        });
    } catch (FFmpegNotSupportedException e) {
        showUnsupportedExceptionDialog();
    } catch (Exception e) {
        Log.d("dd", "EXception no controlada : " + e);
    }
}

private void showUnsupportedExceptionDialog() {
    new AlertDialog.Builder(this)
            .setIcon(android.R.drawable.ic_dialog_alert)
            .setTitle("Not Supported")
            .setMessage("Device Not Supported")
            .setCancelable(false)
            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    finish();
                }
            })
            .create()
            .show();

}
    public String timeTrim(long milliseconds) {
        String finalTimerString = "";
        String minutString = "";
        String secondsString = "";

        // Convert total duration into time
        int hours = (int) (milliseconds / (1000 * 60 * 60));
        int minutes = (int) (milliseconds % (1000 * 60 * 60)) / (1000 * 60);
        int seconds = (int) ((milliseconds % (1000 * 60 * 60)) % (1000 * 60) / 1000);
        // Add hours if there

        if (hours < 10) {
            finalTimerString = "0" + hours + ":";
        } else {
            finalTimerString = hours + ":";
        }


        if (minutes < 10) {
            minutString = "0" + minutes;
        } else {
            minutString = "" + minutes;
        }

        // Prepending 0 to seconds if it is one digit
        if (seconds < 10) {
            secondsString = "0" + seconds;
        } else {
            secondsString = "" + seconds;
        }

        finalTimerString = finalTimerString + minutString + ":" + secondsString;

        // return timer string
        return finalTimerString;
    }

Also use another feature by FFmpeg

===> merge audio to video
String[] cmd = new String[]{"-i", yourRealPath, "-i", arrayList.get(posmusic).getPath(), "-map", "1:a", "-map", "0:v", "-codec", "copy", "-shortest", outputcrop};


===> Flip vertical :
String[] cm = new String[]{"-i", yourRealPath, "-vf", "vflip", "-codec:v", "libx264", "-preset", "ultrafast", "-codec:a", "copy", outputcrop1};


===> Flip horizontally :  
String[] cm = new String[]{"-i", yourRealPath, "-vf", "hflip", "-codec:v", "libx264", "-preset", "ultrafast", "-codec:a", "copy", outputcrop1};


===> Rotate 90 degrees clockwise:
String[] cm=new String[]{"-i", yourRealPath, "-c", "copy", "-metadata:s:v:0", "rotate=90", outputcrop1};


===> Compress Video
String[] complexCommand = {"-y", "-i", yourRealPath, "-strict", "experimental", "-vcodec", "libx264", "-preset", "ultrafast", "-crf", "24", "-acodec", "aac", "-ar", "22050", "-ac", "2", "-b", "360k", "-s", "1280x720", outputcrop1};


===> Speed up down video
String[] complexCommand = {"-y", "-i", yourRealPath, "-filter_complex", "[0:v]setpts=2.0*PTS[v];[0:a]atempo=0.5[a]", "-map", "[v]", "-map", "[a]", "-b:v", "2097k", "-r", "60", "-vcodec", "mpeg4", outputcrop1};
String[] complexCommand = {"-y", "-i", yourRealPath, "-filter_complex", "[0:v]setpts=1.0*PTS[v];[0:a]atempo=1.0[a]", "-map", "[v]", "-map", "[a]", "-b:v", "2097k", "-r", "60", "-vcodec", "mpeg4", outputcrop1};
String[] complexCommand = {"-y", "-i", yourRealPath, "-filter_complex", "[0:v]setpts=0.75*PTS[v];[0:a]atempo=1.5[a]", "-map", "[v]", "-map", "[a]", "-b:v", "2097k", "-r", "60", "-vcodec", "mpeg4", outputcrop1};
String[] complexCommand = {"-y", "-i", yourRealPath, "-filter_complex", "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]", "-map", "[v]", "-map", "[a]", "-b:v", "2097k", "-r", "60", "-vcodec", "mpeg4", outputcrop1};



===> Add two mp3 files 

StringBuilder sb = new StringBuilder();
sb.append("-i ");
sb.append(textSngname);
sb.append(" -i ");
sb.append(mAudioFilename);
sb.append(" -filter_complex [0:0][1:0]concat=n=2:v=0:a=1[out] -map [out] ");
sb.append(finalfile);
---> ffmpeg.execute(sb.toString().split(" "), new ExecuteBinaryResponseHandler()




===> Add three mp3 files

StringBuilder sb = new StringBuilder();
sb.append("-i ");
sb.append(firstSngname);
sb.append(" -i ");
sb.append(textSngname);
sb.append(" -i ");
sb.append(mAudioFilename);
sb.append(" -filter_complex [0:0][1:0][2:0]concat=n=3:v=0:a=1[out] -map [out] ");
sb.append(finalfile);
---> ffmpeg.execute(sb.toString().split(" "), new ExecuteBinaryResponseHandler()
于 2020-06-02T12:49:32.447 回答