ffmpeg — это, в первую очередь, очень удобная консольная утилита для работы с видео, и только потом библиотека, которую можно использовать в своих проектах. Соответственно, и информации по консольной утилите в разы больше. А если учесть, что код ffmpeg, хоть и открытый, но написан так, что черт в нем ногу сломит, то использование данной библиотеки превращается в занятие просто самоубийственное.
Но речь сегодня будет не про то, как юзать ffmpeg. Мы пишем под Android, и поэтому нам нужно сначала собрать библиотеку в пригодный для него вид. Дело в том, что ffmpeg написан на чистом C, и иначе, чем через Native Development Tools, использовать его не получится.
Требования
- Какой-нибудь Linux. В моем случае был Ubuntu на VirtualBox. Я убежденный адепт Майкрософта, но на 64-битной винде мне ничего не удалось.
- Android SDK
- Android NDK r04b. Кстати, неплохо бы иметь о нем некоторое представление.
- Eclipse
Процесс
Создаем проект
Создаем проект для Android в Eclipse, добавляем в него папкуjni
.Получаем исходники ffmpeg
Тут есть два подхода. Можно слить исходники из svn командойsvn checkout svn://svn.ffmpeg.org/ffmpeg/trunk ffmpeg
. А можно взять версию 0.6. ffmpeg довольно быстро развивается, так что разница будет существенна. В данной статье я буду рассматривать процесс сборки из транка, т.к. он меняется, а 0.6 просто выложу собранным.Итак, слили исходники и положили их в папку
jni/ffmpeg
нашего проекта.Конфигурируем ffmpeg
В состав ffmpeg входит shell-скриптconfigure
, который способен сгенерировать .h
и make
файлы для компиляции ffmpeg.configure имеет множество ключей, от которых зависит какие компоненты будут входить в вашу сборку ffmpeg. Полный список настроек можно получить, набрав команду:
./configure --help
Ключей для запуска будет много, так что сделаем отдельный скрипт
config.sh
для запуска:config.sh
PREBUILT=/home/darja/android-ndk/build/prebuilt/linux-x86/arm-eabi-4.4.0 PLATFORM=/home/darja/android-ndk/build/platforms/android-3/arch-arm ./configure --target-os=linux \ --arch=arm \ --enable-nonfree \ --disable-protocols \ --enable-protocol=file \ --disable-network \ --enable-avfilter \ --enable-cross-compile \ --cc=$PREBUILT/bin/arm-eabi-gcc \ --cross-prefix=$PREBUILT/bin/arm-eabi- \ --nm=$PREBUILT/bin/arm-eabi-nm \ --extra-cflags="-fPIC -DANDROID" \ --disable-asm \ --enable-neon \ --enable-armv5te \ --extra-ldflags="-Wl,-T,$PREBUILT/arm-eabi/lib/ldscripts/armelf.x -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtbegin.o $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtend.o -lc -lm -ldl"
PREBUILT
и PLATFORM
, само собой, следует заменить на свои.Выполняем
config.sh
, получаем большой отчет о сконфигурированных для нас частях ffmpeg.Адаптируем к NDK
Скрипт нагенерировал нам кучу файликов. Если бы мы писали под Linux, то делать бы ничего не пришлось. Но у нас Android, и поэтому нужно кое-что допилить.- В файле
/jni/ffmpeg/config.h
поменять#define restrict restrict
на#define restrict
. - В файле
/jni/ffmpeg/libavutil/libm.h
убрать все static-функции. - В папках
libavcodec
,libavcore
,libavfilter
,libavformat
,libavutil
,libpostproc
,libswscale
из всехMakefile
удалить строки:
include $(SUBDIR)../subdir.mak include $(SUBDIR)../config.mak
- Поместить в
/jni/ffmpeg
файлav.mk
с текстом:
/jni/ffmpeg/av.mk
include $(LOCAL_PATH)/../config.mak OBJS := OBJS-yes := MMX-OBJS-yes := include $(LOCAL_PATH)/Makefile # collect objects OBJS-$(HAVE_MMX) += $(MMX-OBJS-yes) OBJS += $(OBJS-yes) FFNAME := lib$(NAME) FFLIBS := $(foreach,NAME,$(FFLIBS),lib$(NAME)) FFCFLAGS = -DHAVE_AV_CONFIG_H -Wno-sign-compare -Wno-switch -Wno-pointer-sign FFCFLAGS += -DTARGET_CONFIG=\"config-$(TARGET_ARCH).h\" ALL_S_FILES := $(wildcard $(LOCAL_PATH)/$(TARGET_ARCH)/*.S) ALL_S_FILES := $(addprefix $(TARGET_ARCH)/, $(notdir $(ALL_S_FILES))) ifneq ($(ALL_S_FILES),) ALL_S_OBJS := $(patsubst %.S,%.o,$(ALL_S_FILES)) C_OBJS := $(filter-out $(ALL_S_OBJS),$(OBJS)) S_OBJS := $(filter $(ALL_S_OBJS),$(OBJS)) else C_OBJS := $(OBJS) S_OBJS := endif C_FILES := $(patsubst %.o,%.c,$(C_OBJS)) S_FILES := $(patsubst %.o,%.S,$(S_OBJS)) FFFILES := $(sort $(S_FILES)) $(sort $(C_FILES))
- Создать в
/jni/
файлAndroid.mk
:
/jni/Android.mk
include $(all-subdir-makefiles)
- Создать в
/jni/ffmpeg
файлAndroid.mk
:
/jni/ffmpeg/Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_STATIC_LIBRARIES := libavcore libavformat libavcodec libavutil libpostproc libswscale LOCAL_MODULE := ffmpeg include $(BUILD_SHARED_LIBRARY) include $(call all-makefiles-under,$(LOCAL_PATH))
- Создать в
/jni/ffmpeg/libavformat
файлAndroid.mk
:
/jni/ffmpeg/libavformat/Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) include $(LOCAL_PATH)/../av.mk LOCAL_SRC_FILES := $(FFFILES) LOCAL_C_INCLUDES := \ $(LOCAL_PATH) \ $(LOCAL_PATH)/.. LOCAL_CFLAGS += $(FFCFLAGS) LOCAL_CFLAGS += -include "string.h" -Dipv6mr_interface=ipv6mr_ifindex LOCAL_LDLIBS := -lz LOCAL_STATIC_LIBRARIES := $(FFLIBS) LOCAL_MODULE := $(FFNAME) include $(BUILD_STATIC_LIBRARY)
- Создать в
/jni/ffmpeg/libavcodec
файлAndroid.mk
/jni/ffmpeg/libavcodec/Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) include $(LOCAL_PATH)/../av.mk LOCAL_SRC_FILES := $(FFFILES) LOCAL_C_INCLUDES := \ $(LOCAL_PATH) \ $(LOCAL_PATH)/.. LOCAL_CFLAGS += $(FFCFLAGS) LOCAL_LDLIBS := -lz LOCAL_STATIC_LIBRARIES := $(FFLIBS) LOCAL_MODULE := $(FFNAME) include $(BUILD_STATIC_LIBRARY)
- Поместить в папки
libavcore
(к слову, в релизе такой папки нет),libavfilter
,libavutil
,libpostproc
,libswscale
по файлуAndroid.mk
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) include $(LOCAL_PATH)/../av.mk LOCAL_SRC_FILES := $(FFFILES) LOCAL_C_INCLUDES := \ $(LOCAL_PATH) \ $(LOCAL_PATH)/.. LOCAL_CFLAGS += $(FFCFLAGS) LOCAL_STATIC_LIBRARIES := $(FFLIBS) LOCAL_MODULE := $(FFNAME) include $(BUILD_STATIC_LIBRARY)
Компилируем
Из корня проекта запускаем скриптndk-build
(он находится в NDK) и идем пить чай, потому что компилироваться будет долго.Когда компиляция будет окончена, идем в папку
obj
и видим:Собственно, эти файлы нам и нужны.
А дальше?
Теперь кратко о том, что с этими файлами делать. Переносим папкуobj
в проект, в котором планируется использовать ffmpeg (на этом месте можно возвращаться обратно в Windows). Добавляем также папку jni
, в которой будет находиться наш код для NDK. Также нужно будет добавить заголовочные файлы ffmpeg. Получится примерно вот такая картина:Дабы убедиться, что ffmpeg действительно подключается и работает, напишем какой-нибудь код. Создадим в
jni
файл mylib.c
:mylib.c
#include <jni.h> #include <android/log.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #define LOG_TAG "mylib" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) jint Java_demo_ffmpeg_MainActivity_logFileInfo(JNIEnv * env, jobject this, jstring filename) { av_register_all(); AVFormatContext *pFormatCtx; const jbyte *str; str = (*env)->GetStringUTFChars(env, filename, NULL); if(av_open_input_file(&pFormatCtx, str, NULL, 0, NULL)!=0) { LOGE("Can't open file '%s'\n", str); return 1; } else { LOGI("File was opened\n"); LOGI("File '%s', Codec %s", pFormatCtx->filename, pFormatCtx->iformat->name ); } return 0; }
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_LDLIBS += -llog LOCAL_STATIC_LIBRARIES := libavformat libavcodec libpostproc libswscale libavutil libavcore LOCAL_C_INCLUDES += $(LOCAL_PATH)/ffmpeg LOCAL_SRC_FILES := mylib.c LOCAL_MODULE := mylib include $(BUILD_SHARED_LIBRARY) LOCAL_PATH := $(call my-dir) LOCAL_C_INCLUDES += $(LOCAL_PATH)/ffmpeg include $(all-subdir-makefiles)
libs
, в которой окажется библиотека libmylib.so
. Теперь можно вызывать функции нашей библиотеки в java-коде. Конечно, это уже детали NDK, но для полноты картины приведу и этот код:MainActivity.java
public class MainActivity extends Activity { private static native int logFileInfo(String filename); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); logFileInfo("/sdcard/sample.mpg"); } static { System.loadLibrary("mylib"); } }
09-19 15:47:49.379: INFO/mylib(851): File was opened 09-19 15:47:49.379: INFO/mylib(851): File '/sdcard/sample.mpg', Codec mpegКак видно, ffmpeg работает.
Заключение
Надеюсь, данная инструкция сэкономит кому-нибудь пару-тройку дней жизни.Кстати, статья основана на мануале китайского товарища, прочитанном с помощью Google Translate. Спасибо ему, а то биться мне головой об стену гораздо дольше.
Может, напишу как-нибудь об использовании самой библиотеки. Возможности у нее большие, информации крайне мало, код кривой, а мне со всем этим удалось-таки что-то сделать.
Дарья, вы - гений! Недавно ставилась задача слияния/конвертирования видео и мы не справились... даже как подступиться не придумали... теперь понятно - как надо было идти!
ОтветитьУдалитьP.S. просто любопытно - а вы код будете открывать под GPL, дабы не нарушить лицензию?
Антон, так ведь: --enable-nonfree
ОтветитьУдалитьКак я понял, ffmpeg можно собирать и под gpl и нет.
Тут главный момент в том, что ffmpeg распространяется под LGPL. Т.е. библиотеку можно линковать с приложением, распространяемым под любой лицензией
ОтветитьУдалитьВставлю свои пять копеек.
ОтветитьУдалитьТоже собирал ffmpeg под Android, но его возможности использовал не через API, а сделал обертку вокруг функции main и далее обращался к собранной либе как из командной строки. Это намного удобнее, т.к. документации по командам ffmpeg'а во много раз больше.
@jeck_landin
ОтветитьУдалитьХитрый ты.
Кстати, господа, мне тут недавно сообщили, что по данной инструкции ffmpeg собирается только для NDK r4. А так как править ее я буду не раньше, чем снова столкнусь с подобной задачей, прошу иметь в виду и разбираться самостоятельно.
@darja
ОтветитьУдалитьНасколько я помню - насчет собирается или нет не знаю, но вот то что уже собрано работало у меня с таким как у вас Abdroid.mk только в NDK 4 версии. Чтобы заработало в 5 вставил такую строку в Android.mk:
LOCAL_LDLIBS += -llog -lavformat -lavcodec -lavutil -lavcore -L/путь к файлам типа libavcodec.a и тд и тп
Вобщем теперь у меня работает в любой НДК:) Если что - надеюсь моя строка поможет
Да уж, попытка собрать по инструкции с ndk5 обошлась мне в 3 дня ...
ОтветитьУдалитьА есть продолжение файлика mylib.c, а то я как явер не знаю С++, а либа эта очень заинтересовала?
ОтветитьУдалитьНарод, подскажите новичку. В сях я совсем не силен, но придется, похоже, разбираться с ffmpeg для Android.
ОтветитьУдалитьМне не понятен такой момент. Если библиотека компилируется для определенной архитектуры процессоров, то как насчет совместимости? Смартфоны то на разных процах постороены...
а где
ОтветитьУдалитьenv->ReleaseStringUTFChars(filename, str);
Вы пробовали запись аудио на OpenAl?
(не удалось alcCaptureOpenDevice с NULL именем)
Добавлю. Для того что бы оно собралось по ndk-r5 надо в файл /jni/ffmpeg/av.mk в флаги добавить -std=c99
ОтветитьУдалитьполсе этого сам ffmpeg соберется. Далее все получившиеся *.a файлы складываем в папку jni/lib нашего проекта где мы будем использовать ffmpeg и изменяем приведенный в статье Anroid.mk на следующий
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := lib/libavformat.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := lib/libavcodec.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libpostproc
LOCAL_SRC_FILES := lib/libpostproc.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := lib/libswscale.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := lib/libavutil.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_LDLIBS += -llog
LOCAL_STATIC_LIBRARIES := libavformat libavcodec libpostproc libswscale libavutil
LOCAL_C_INCLUDES += $(LOCAL_PATH)/ffmpeg
LOCAL_SRC_FILES := mylib.c
LOCAL_MODULE := mylib
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH := $(call my-dir)
LOCAL_C_INCLUDES += $(LOCAL_PATH)/ffmpeg
include $(all-subdir-makefiles)
надеюсь кому-нибудь помог
А у кого-нибудь собралось в NDK без опции:
ОтветитьУдалить--disable-asm
Дело в том, что эта опция убирает все ARM-оптимизации процессора и код работает в разы медленнее, как минимум в 2 раза медленнее.
У меня, если убирается эта опция не компилится под NDK. Вываливается ошибка что такая-то ассемблерная команда не поддерживается выбранным процессором.
Есть какие-нибудь идеи как этого избежать?
Разобрался, cflags нужно продублировать в av.mk, тогда компилится нормально. С аппаратным ускорением, кстати, работает раз 5-6 быстрее чем в примере с опцией --disable-asm
ОтветитьУдалитьDima L. выложите свой конфиг с --disable-asm пожалуйста, никак не получается заставить компилироваться ffmpeg с этим параметром.
ОтветитьУдалитьДобрый день.
ОтветитьУдалитьДарья, спасибо. По вашей статье таки собрал ffmpeg под android.
Но у меня есть вопрос:
jeck_landin пишет что написал обертку и теперь использует ffmpeg как из командной строки. Кто нибудь подскажет как это можно сделать? Что то я не разбирусь :(
А то перекопал кучу инфы но с API разбираться тяжело... я далек от СИ.
У меня благодаря Дарье получилось собрать FFMPEG в таком виде, для NDK r5 пришлось ещё пошаманить.
ОтветитьУдалитьДля сборки с ARM-оптимизациями очень долго плясал с бубном, переделывая config.mak и config.h. Потом для включения ARM-версий исходников для libavcodec потребовалось кусок arm/Makefile переносить на верхний уровень.
Но когда всё собралось и слинковалось, ARM-оптимизация не очень помогла. avcodec_decode_video2() не ускорился и в 2 раза, sws_scale() для YUV->RGB как выполнялся 20 мс для VGA-кадра, так и продолжает (впрочем, в libswscale нет ARM-ветки кода, что странно). А ведь я ещё добавил опцию -ftree-vectorize.
Браво! Не "хило" так, как для убежденного адепта Майкрософта!
ОтветитьУдалитьМожет кому будет интересно в конфигурационном скрипте:
ОтветитьУдалить--extra-ldflags="-Wl,-T,$PREBUILT/arm-eabi/lib/ldscripts/armelf.x -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtbegin.o $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtend.o -lc -lm -ldl"
Вся "лабуда" нивелируется аргументом -nostdlib, поэтому достаточно только указать эту опцию.
Но это не совсем хороший подход - юзать nostdlib.