نقل FFmpeg إلى Android
البداية
بالنسبة للطلاب الذين لم يتعرفوا بعد على FFmpeg، يمكنهم أولاً الاطلاع على الأوامر الأساسية الشائعة لـ FFmpeg، والبحث بعمق في وظائفه – مثل دمج الصوت والفيديو، تشغيل أنواع مختلفة من الترميز، قص الفيديو، دمج عدة صور في فيديو وخلط الصوت، تحويل التنسيقات، وغيرها. يمكن أن توفر FFmpeg وظائف قوية ومثيرة للاهتمام للعديد من سيناريوهات التطبيق.
باستخدام تطبيقات مثل “التعليق الصوتي” كمثال، فإن التعليق الصوتي ليس فقط ممتعًا للغاية، بل غالبًا ما يجعل المنتج أكثر جاذبية. نحن نخطط لتطوير وحدة تعليق صوتي، وبالتالي نظرنا إلى FFmpeg، وحاولنا نقله إلى منصة Android. استغرق الأمر حوالي 3 إلى 4 أيام، وجربنا عدة إصدارات، ولم تنجح العديد من المقالات على الإنترنت، حتى وجدنا برنامجًا تعليميًا بعنوان “استخدام ffmpeg على android واستدعاء الواجهات”، والذي أخيرًا ساعدنا في تحقيق النجاح. أظهرت الاختبارات العملية أن فقط مزيج FFmpeg 1.2 مع ndk-r9 يمكن نقله بنجاح.
الفكرة
FFmpeg 是一个用 C 语言编写的项目,其中包含一个 main()
函数。我们的目标是:
- استخدم Android NDK لتجميع
libffmpeg.so
. - باستخدام هذه المكتبة، قم بتجميع وتعديل ملف
ffmpeg.c
، وقم بتغيير اسم الدالة الأصليةmain()
إلىvideo_merge(int argc, char **argv)
، مما يسمح باستدعائها مباشرة من JNI لإجراء عمليات مثل دمج الفيديو.
على سبيل المثال، يمكن تحقيق تركيب الفيديو بطريقة مشابهة لما يلي (المقابل لسطر الأوامر ffmpeg -i src1 -i src2 -y output
):
video_merge(5, argv); // حيث يتم محاكاة وسائط سطر الأوامر بواسطة argv
البيئة
- نظام التشغيل: Ubuntu 12.04
- إصدار FFmpeg: 1.2
- إصدار NDK: ndk-r9
قبل البدء، يُنصح بالاطلاع على بعض البرامج التعليمية ذات الصلة، وعند مواجهة أي مشاكل، يمكنك العودة إلى هذه المقالة للمقارنة، لتجنب اتخاذ مسارات طويلة وغير ضرورية.
تعديل الواجهة وملف Android.mk
عند كتابة واجهة JNI لـ FFmpeg، تحتاج إلى كتابة ملف Android.mk
لربط المكتبات، وبالتالي إنشاء ملف .so
قابل للاستخدام. بعض نماذج Android.mk
قد لا تعمل مباشرة في بيئات مختلفة. وظيفتها هي إخبار NDK بأي ملفات المصدر التي تحتاج إلى الترجمة، وإلى أي مكتبة يجب الربط، وغيرها من المعلومات.
لقد اعتمدت أسلوبًا يتمثل في “التجميع مرتين، ثم الربط”:
- أولاً، قم بتجميع مكتبة مشتركة تُسمى
myffmpeg
. - في وحدة أخرى تُسمى
ffmpeg-jni
، قم بربطmyffmpeg
داخلها، ثم قم بإنشاء ملف.so
المطلوب في النهاية.
بالإضافة إلى ذلك، يجب وضع ملف libffmpeg.so
الذي تم تجميعه في دليل jni
لضمان العثور عليه أثناء عملية الربط.
تصحيح FFmpeg
عندما تعمل مع FFmpeg، قد تواجه مشاكل تتطلب منك تصحيح الأخطاء. إليك بعض الخطوات التي يمكنك اتباعها لتصحيح FFmpeg:
1. تفعيل وضع التصحيح (Debug Mode)
يمكنك تفعيل وضع التصحيح في FFmpec باستخدام الخيار -loglevel debug
. هذا سيظهر معلومات تفصيلية عن العملية التي يقوم بها FFmpeg.
ffmpeg -loglevel debug -i input.mp4 output.avi
2. استخدام -report
لإنشاء تقرير
يمكنك استخدام الخيار -report
لإنشاء تقرير يحتوي على معلومات مفصلة عن العملية. هذا التقرير يمكن أن يكون مفيدًا في تحديد المشاكل.
ffmpeg -i input.mp4 -report output.avi
سيتم إنشاء ملف تقرير في نفس الدليل الذي يتم تنفيذ الأمر منه.
3. فحص ملفات السجل (Log Files)
إذا كنت تواجه مشاكل معينة، يمكنك فحص ملفات السجل التي يتم إنشاؤها بواسطة FFmpeg. هذه الملفات تحتوي على معلومات مفصلة عن الأخطاء والتحذيرات.
4. استخدام -v
لزيادة مستوى التفاصيل
يمكنك استخدام الخيار -v
لزيادة مستوى التفاصيل في المخرجات. على سبيل المثال، -v verbose
أو -v debug
.
ffmpeg -v debug -i input.mp4 output.avi
5. التأكد من الإصدار والتكوين
أحيانًا تكون المشاكل مرتبطة بإصدار معين من FFmpeg أو التكوين الذي تم استخدامه لبناء FFmpeg. يمكنك التحقق من الإصدار والتكوين باستخدام الأمر التالي:
ffmpeg -version
6. استخدام -benchmark
لقياس الأداء
إذا كنت تواجه مشاكل في الأداء، يمكنك استخدام الخيار -benchmark
لقياس الوقت الذي تستغرقه كل عملية.
ffmpeg -benchmark -i input.mp4 output.avi
7. التأكد من توفر المكتبات المطلوبة
تأكد من أن جميع المكتبات المطلوبة متوفرة ومثبتة بشكل صحيح. يمكنك استخدام الأمر ffmpeg -buildconf
للتحقق من التكوين والمكتبات المستخدمة.
8. استخدام -f
لتحديد التنسيق
إذا كنت تواجه مشاكل في تحديد التنسيق، يمكنك استخدام الخيار -f
لتحديد التنسيق يدويًا.
ffmpeg -f mp4 -i input.mp4 output.avi
9. التأكد من صحة ملف الإدخال
أحيانًا تكون المشاكل مرتبطة بملف الإدخال نفسه. تأكد من أن ملف الإدخال صالح ولا يحتوي على أخطاء.
10. طلب المساعدة من المجتمع
إذا لم تتمكن من حل المشكلة بنفسك، يمكنك طلب المساعدة من مجتمع FFmpeg عبر منصات مثل Stack Overflow أو قوائم البريد الإلكتروني الخاصة بـ FFmpeg.
باتباع هذه الخطوات، يمكنك تصحيح معظم المشاكل التي تواجهها أثناء العمل مع FFmpeg.
بعد نقل FFmpeg، غالبًا ما نحتاج إلى تصحيح الأخطاء في طبقة C من أجل استدعاء الوظائف عبر JNI. إذا كان بإمكاننا رؤية سجلات الإخراج التفصيلية كما في سطر الأوامر، فسيكون من الأسهل تحديد المشكلات.
في Eclipse، يمكنك الضغط على مفتاح Ctrl والنقر على موقع استدعاء مشابه لـ av_log
لتتبع تنفيذ الدالة av_log_default_callback
الموجودة في ffmpeg/libavutil/log.c
. هذه الدالة تستدعي __android_log_print
الخاص بـ Android لطباعة الرسائل في Logcat. من خلال مراجعة هذه المخرجات، يمكنك معرفة الحالة الداخلية لـ FFmpeg، مما يساعد في استكشاف الأخطاء مثل فشل التوليف أو عدم دعم ترميز معين.
أحيانًا، قد يلقي FFmpeg استثناءً يتسبب في تعطل التطبيق. يمكن استخدام الأمر التالي لتحديد المشكلة:
adb shell logcat | ndk-stack -sym obj/local/armeabi
ملاحظة: الكود أعلاه يستخدم أوامر adb
و ndk-stack
لتحليل سجلات الأخطاء (logcat) في تطبيقات Android التي تستخدم NDK (Native Development Kit). لا يتم ترجمة الأوامر أو المسارات لأنها أسماء أوامر محددة في نظام Android.
إذا كان لدى FFmpeg الأصلي main()
في النهاية exit(0)
، يرجى تذكر تعليقها، وإلا سيؤدي ذلك إلى خروج التطبيق.
تسريب الذاكرة وحلول Service
تسريب الذاكرة (Memory Leak) هو مشكلة شائعة في تطوير التطبيقات، حيث يتم تخصيص جزء من الذاكرة ولكن لا يتم تحريره بعد الانتهاء من استخدامه. مع مرور الوقت، يمكن أن يؤدي ذلك إلى استهلاك كبير للذاكرة، مما يؤثر على أداء التطبيق وقد يتسبب في تعطيله.
في سياق تطبيقات Android، يمكن أن تحدث تسريبات الذاكرة عند استخدام Service
بشكل غير صحيح. على سبيل المثال، إذا تم تشغيل Service
ولم يتم إيقافه بشكل صحيح، فقد يستمر في استهلاك الذاكرة حتى بعد أن لا تكون هناك حاجة إليه.
حلول لتجنب تسريب الذاكرة مع Service
- استخدام
IntentService
بدلاً منService
:IntentService
هي فئة فرعية منService
تقوم تلقائيًا بإيقاف نفسها بعد الانتهاء من معالجة جميع الطلبات. هذا يقلل من خطر تسريب الذاكرة.
public class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { // تنفيذ المهام هنا } }
- إيقاف
Service
بشكل صحيح:- إذا كنت تستخدم
Service
مباشرة، تأكد من إيقافه بشكل صحيح بعد الانتهاء من استخدامه. يمكن القيام بذلك عن طريق استدعاءstopSelf()
داخلService
.
public class MyService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { // تنفيذ المهام هنا stopSelf(); // إيقاف الـ Service بعد الانتهاء return START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } }
- إذا كنت تستخدم
- استخدام
JobScheduler
أوWorkManager
:- بدلاً من استخدام
Service
، يمكنك استخدامJobScheduler
أوWorkManager
لإدارة المهام الخلفية. هذه الأدوات مصممة لإدارة الموارد بشكل أكثر كفاءة وتقليل خطر تسريب الذاكرة.
// مثال باستخدام WorkManager WorkManager workManager = WorkManager.getInstance(context); workManager.enqueue(new OneTimeWorkRequest.Builder(MyWorker.class).build());
- بدلاً من استخدام
- مراقبة الذاكرة باستخدام أدوات مثل
LeakCanary
:LeakCanary
هي مكتبة تساعد في اكتشاف تسريبات الذاكرة في تطبيقات Android. يمكنك إضافتها إلى مشروعك لمراقبة الذاكرة واكتشاف التسريبات بسهولة.
dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' }
باتباع هذه الممارسات، يمكنك تقليل خطر تسريب الذاكرة في تطبيقات Android الخاصة بك، مما يؤدي إلى تحسين الأداء وتجربة المستخدم.
بعد اكتمال التوليف، إذا ظهر خطأ "INVALID HEAP ADDRESS IN dlfree ffmpeg"
عند الاستدعاء مرة أخرى، فمن المرجح أن يكون ذلك بسبب عدم تحرير ذاكرة FFmpeg بشكل كامل. إحدى الحلول الوسطية هي وضع عملية التوليف في Service
منفصل، وبعد اكتمال التوليف، يتم إنهاء هذا الـ Service لتنظيف الموارد.
<!-- AndroidManifest.xml -->
<service android:name=".FFmpegService" />
من خلال تسجيل Receiver
وما شابه، وإنهاء الخدمة بشكل تلقائي بعد اكتمال التركيب، يمكن تجنب مشاكل الذاكرة الناتجة عن الاستدعاءات المتكررة.
المشكلات المحتملة
- مشاكل تشغيل ملفات AAC
قد لا تتمكن بعض الأجهزة (مثل Xiaomi 2s) من تشغيل الصوت المُشفّر بتنسيق AAC باستخدامMediaPlayer
الافتراضي. - دعم غير كافٍ للمُشفِّرات
إذا كنت ترغب في دعم تنسيقات مثل AMR-NB وMP3، يجب تمكين الخيارات المناسبة يدويًا عند تجميع FFmpeg. إذا لم يتمكن البرنامج النصي للتجميع من العثور على المكتبات أو ملفات الرأس ذات الصلة، فسيتم إيقاف العملية وإرجاع خطأ. - سرعة التوليف
قد يستغرق توليف مقطع فيديو مدته 10 ثوانٍ بدقة 1280×720 مع مزج الصوت ما بين عشرات الثواني إلى دقيقة كاملة. من حيث تجربة المستخدم، قد يكون من الأفضل السماح للمستخدم بمعاينة الصوت قبل اتخاذ قرار التوليف النهائي.
في التنفيذ الفعلي لتطبيقات دبلجة الصوت، تكون الممارسات الشائعة كالتالي:
- تحميل الفيديو الأصلي، والترجمات، وملف الصوت الذي تمت إزالة المقاطع التي تحتاج إلى دبلجة منه مسبقًا.
- تسجيل صوت المستخدم، وعند التوليف، يتم فقط دمج التسجيل مع المقاطع الصامتة.
- إذا لم تكن راضيًا عن وقت التوليف المحلي، يمكنك اختيار تحميل بيانات الصوت والفيديو إلى الخادم، حيث يتم التوليف على جانب الخادم، ثم يتم التنزيل بعد الانتهاء.
استخدام NDK في Eclipse
ليس من الضروري إدخال ndk-build
في سطر الأوامر. يمكنك ببساطة النقر بزر الماوس الأيمن على المشروع في Eclipse، ثم اختيار Android Tools → Add Native Support، وبعد ذلك سيتم تنفيذ ndk-build
تلقائيًا في كل مرة تضغط فيها على “Run”.
إنشاء ملفات رأس JNI بنقرة واحدة
كتابة ملفات رؤوس دوال JNI يمكن أن تكون عملية مرهقة، ولكن يمكن تسهيلها باستخدام الأمر javah
الذي يقوم بإنشائها تلقائيًا.
في بيئة Eclipse، يمكنك تكوين الأمر كأداة خارجية واستخدام أمر مشابه لما يلي لإنشاء ملف الرأس:
javah -jni -classpath bin/classes -d jni com.example.ffmpeg.MyFFmpeg
بعد التنفيذ، سيتم إنشاء ملف مشابه لـ com_example_ffmpeg_MyFFmpeg.h
في دليل jni
. بعد ذلك، كل ما عليك فعله هو تضمينه (#include
) في كود C الخاص بك وتنفيذ الوظائف المقابلة.
الخلاصة
يتطلب نقل FFmpeg إلى Android معرفة متعددة الجوانب، بما في ذلك تكوين بيئة NDK، وتجميع وربط C/C++، واستدعاء JNI، وتشفير وفك تشفير الصوت والفيديو، وغيرها. إذا واجهت مشاكل مثل عدم القدرة على التوليف، أو عدم دعم بعض التنسيقات، أو أخطاء في الربط، فأنت بحاجة إلى التحقق بعناية من التكوين وسجلات الإخراج. آمل أن تساعدك هذه المقالة في تجنب بعض المشاكل. إذا كنت تستخدم FFmpeg أيضًا، فلا تتردد في مشاركة تجاربك أو مشاكلك في قسم التعليقات، للتبادل والتعلم المتبادل.