安卓上的FFmpeg

222

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

  1. 您是否有整合FFmpeg到Android / StageFright的步骤/流程/代码/示例?

  2. 请指导我如何使用此库进行多媒体播放?

  3. 我有一个需求,我已经有音频和视频传输流,需要将其提供给FFmpeg进行解码/渲染。由于IOMX API基于OMX,无法在此处插入FFmpeg,因此该怎么做呢?

  4. 我也找不到有关播放所需使用的FFmpeg API的文档。


7
这很有趣,我也感到好奇。 - Axarydax
5
你是如何编译FFmpeg以获取.so文件的?你能分享一下你所遵循的步骤吗?我正在使用带有Cygwin-1.7.9和NDK R5的Windows系统。请帮帮我。 - Swathi EP
请帮我解决这个问题:http://stackoverflow.com/questions/14157030/how-to-include-ffmpeg-functions-in-android-code/14157200#14157200,我不知道在哪里包含这个函数并运行! - TharakaNirmana
我已经编写了简单的构建脚本,用于编译最新的FFmpeg(以及librtmp)适用于Android:https://github.com/OnlyInAmerica/FFmpeg-Android - dbro
将该库封装成易于集成的库。https://github.com/madhavanmalolan/ffmpegandroidlibrary - Madhavan Malolan
显示剩余4条评论
10个回答

111
以下是我在Android上使用ffmpeg的步骤:
  1. 为Android构建ffmpeg的静态库。这可以通过使用Android Build System构建olvaffe's ffmpeg android port(libffmpeg)来实现。只需将源代码放置在/external下并运行make即可。您还需要从Android构建中提取bionic(libc)和zlib(libz),因为ffmpeg库依赖它们。
  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二进制文件本身就是一个很好的示例),here有一个基本教程。最好的文档可以在头文件中找到。

祝你好运 :)


7
建立FFmpeg用于Android的答案已有许多链接,这仍然是最佳解决方案吗?Android Build System链接已失效,它应该是什么?有一堆工具包可用于辅助使用NDK进行构建。然而,对我来说,它们都以不同的构建错误失败,并且似乎有点过时。是否有任何理由为什么不能发布一个已构建的静态FFmpeg库? - Rob Lourens
7
为了回答我的问题,我发现这个代码仓库对于构建ffmpeg和JNI包装器非常有用 - https://github.com/andynicholson/android-ffmpeg-x264 - Rob Lourens

73

由于各种原因,多媒体技术的效率提高从未容易过。ffmpeg是一项不断改进它的努力,支持不同格式的编解码器和容器。

现在来回答“如何使用这个库”的问题,我得说在这里简单写下并不那么容易。但我可以通过以下方式指导你:

1) 在源代码中的ffmpeg目录下,你会找到output_example.c或api_example.c文件。在这里,你可以看到编码/解码的代码。你会明白应该调用ffmpeg中哪些API作为你的第一步。

2) Dolphin播放器是一个针对Android的开源项目。目前还有一些bug,但开发者在不断地修复中。在该项目中,你可以使用整个设置来继续你的研究。这里是从code.google.com链接到该项目,或在终端中运行命令"git clone https://code.google.com/p/dolphin-player/"。你会看到两个名为P和P86的项目,你可以任选其一。

我想提供的额外提示是,在构建ffmpeg代码时,你需要在build.sh中启用muxers/demuxers/encoders/decoders所需的格式。否则相应的代码将不会被包含在库中。我花了很多时间才意识到这一点。所以想与你分享。

一些基础知识: 当我们说视频文件时,例如:avi,它标识了同时包含音频和视频的文件。

视频文件 = 视频 + 音频


视频 = 编解码器 + 多路复用器 + 分离器

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

=>视频 = 编码器 + 解码器 + 多路复用器 + 分离器(Mpeg4 + Mpeg4 + avi + avi - 以avi为例)


音频 = 编解码器 + 多路复用器 + 分离器

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

=>音频 = 编码器 + 解码器 + 多路复用器 + 分离器(mp2 + mp2 + avi + avi - 以avi为例)


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

多路复用器/分离器用于在编解码时组合/分离文件中的帧。

因此,如果你想使用avi格式,则需要启用视频组件+音频组件。

例如,在avi格式中,你需要启用以下组件:mpeg4编码器、mpeg4解码器、mp2编码器、mp2解码器、avi多路复用器、avi分离器。

呼~~~

编程式地,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)

希望我在这之后没有让您更加困惑...

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


1
嘿,非常感谢你提供的信息,你真的帮了我很多。如果以后我需要帮助,你能帮我吗?谢谢! - idish
请问可否加您的Skype/MSN或任何其他聊天平台好友?我有一些问题想向您请教,谢谢。 - idish
2
当然可以!但是我的在线存在有点低...除非非常必要,否则我不会登录Skype。您可以通过电子邮件与我联系处理任何重要事项。邮箱:mantykuma@gmail.com - Sandeep

16

13

不确定,我猜可能不会有问题,新版本的iOS中没有什么想到的可能会导致这个出错。当我发布这篇文章时,我还使用着10.7或者10.6版本。 - Guy
你知道我怎么用JNI实现将3gp转换为音频吗? - Mr.G

11

我做了一个小项目用于配置和构建X264和FFMPEG,使用Android NDK.主要缺少的是一个不错的JNI接口使其可以通过Java访问,但这相对来说是容易的部分。当我开始为自己的使用制作好JNI接口时,我会将其推进去。

与olvaffe的构建系统相比,它的好处是不需要Android.mk文件构建库,而只需使用常规的makefile和工具链。这使得它在从FFMPEG或X264拉取新更改时,停止工作的可能性大大降低。

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


Nick,你的项目在OS X 10.7上无法编译 libx264.a(common.o):在函数“x264_param_parse”中: common.c:(.text+0x2864):对“_DefaultRuneLocale”的引用未定义 collect2: ld 返回了1个退出状态 make: *** [x264] 错误1 - Yuriy Solovyov

7

3
这个封装器非常非常非常非常慢。将200张图片转换成视频需要50-60秒……但通常情况下,ffmpeg只需4-5秒即可完成该任务。 - Arsen Sench
这个项目已经无法工作了。你有其他的资源吗? - Ajeet
@ArsenSench,你有其他的解决方案吗? - Akash Dubey
你可以试一试这个:https://github.com/tanersener/ffmpeg-kit - radiolondra
@jmartinalonso 它在 Android 10 及以上版本上无法工作。 - Bhauraj Biradar

3

这个项目还没有被提到,有些奇怪:Appunite的AndroidFFmpeg

它提供了非常详细的逐步指南,用于复制/粘贴到命令行,方便像我这样懒惰的人))。


3

1
看起来你已经编译了FFmpeg v2.8.4,有没有升级FFmpeg的计划?我们正在寻找具有最新版本(可能是3.2或3.4)的FFmpeg的Android解决方案。 - sappu
是的,我打算将其迁移到3.x版本。 https://github.com/madhavanmalolan/ffmpegandroidlibrary/milestone/1您可以尝试在此处修改构建脚本并编译为3.4版本。 https://github.com/madhavanmalolan/ffmpegandroidlibrary/wiki/Building-from-source - Madhavan Malolan
谢谢@Madhvan。我正在Windows上构建ffmpeg库。只想知道在https://github.com/madhavanmalolan/ffmpegandroidlibrary/wiki/Building-from-source中需要更改哪些内容才能进行构建? - sappu

3

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

(lame和FFmpeg:https://github.com/intervigilium/liblamehttp://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.
        }
    }
}

你对Guardian Project有什么经验? - X.Y.

2

首先,添加FFmpeg库的依赖。

implementation 'com.writingminds:FFmpegAndroid:0.3.2'

然后加载活动。
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;
    }

同时使用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()

我已经使用了这个解决方案,它完美地工作了,但视频质量太差,只减少了3MB。还有其他的解决方案吗? - Krutika Chotara
1
不要使用 -vcodec libx264,可以选择以下替代方案之一: 1)-vcodec mpeg4(使用比默认值更高的比特率) 2)-b:v 200k 3)或者选择一个- qscale:v值从1-31,其中较低的值会产生更高的比特率,因此通常质量更好。 - jay patoliya
@jaypatoliya,我想将一个mp3文件转换为视频文件,并附带一张图片。你的代码怎么做呢? - Noor Hossain

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